Convert Incoming Twilio Messages to Audio with Transformers.js
Time to read: 24 minutes
Convert Incoming Twilio Messages to Audio with Transformers.js TTS
In this tutorial, you will build a web app that not only retrieves and displays the latest messages sent to your Twilio phone number but also converts them to audio using Text-to-Speech (TTS).
To build this application, you will use the Twilio Programmable Messaging REST API to retrieve the most recent messages sent to your Twilio number. Then, you will display these messages in the web app. You will use Transformers.js TTS functionality to convert the messages to audio, the wavefile library to process the audio, and the Audiomotion-analyzer to create and display the audio waveforms. The IDB-Keyval library for audio storage and retrieval. You will also use Twilio Sync to share incoming messages detected on the server with the client app.
The Twilio Programmable Messaging REST API is a service that allows developers to send, receive, and retrieve SMS and MMS messages programmatically using Twilio's infrastructure.
Twilio Sync is a service that enables real-time data synchronization between connected devices and applications, allowing seamless collaboration and communication.
Transformers.js is a library that mirrors the functionality of Hugging Face’s transformers Python library , enabling you to run in the browser, the same pre-trained models with a highly similar API.
The wavefile library is a tool for working with WAV audio files in JavaScript or Node.js. It provides functions to create, manipulate, and read WAV files, allowing developers to handle audio data efficiently.
The Audiomotion-analyzer library is designed for analyzing audio in web applications. It facilitates tasks such as generating visualizations of audio waveforms and processing audio data using JavaScript.
The IDB-Keyval library simplifies the usage of the IndexedDB API for key-value storage in web applications. It provides a simple and convenient interface for storing and retrieving data in the IndexedDB database.
By the end of this tutorial, you will have a web app that looks similar to the following:
Tutorial Requirements
To follow this tutorial you will need the following:
- A Twilio account - Sign up here
- A Ngrok account and the Ngrok CLI
- A basic understanding of Javascript
- A basic understanding of how to use Twilio SMS and Sync APIs to build a web app
- Node.js v18+ installation
- Git installation
Getting the boilerplate code
In this section, you will clone a repository containing the boilerplate code needed to build the application.
Open a terminal window and navigate to a suitable location for your project. Run the following commands to clone the repository containing the boilerplate code and navigate to the boilerplate directory:
This boilerplate code includes an Express.js project that serves the client application.
This Node.js application comes with the following packages:
- express: is a lightweight Node.js framework used to build web applications and APIs.
- body-parser: is a middleware for Express.js that extracts the entire body portion of an incoming request stream and exposes it on
req.body
. - node-dev: is a development tool for Node.js that automatically restarts the server upon detecting changes in the source code.
- dotenv: is a Node.js package that facilitates loading environment variables from a .env file into process.env.
- twilio: is a package that allows you to interact with the Twilio API.
- twilio-sync: is a package specifically designed to interact with the Twilio Sync API.
- uuid: is a package for generating universally unique identifiers (UUIDs).
Use the following command to install the packages mentioned above:
Understand the public directory
The public directory contains a file named index.html and the following subdirectories: css, js, and audio.
The index.html file sets up the structure for the client application’s web page, it incorporates Bootstrap for styling and functionality, and Twilio Sync Javascript SDK for synchronizing data across multiple clients. This file defines elements for displaying messages and notifications, and additionally, it defines an audio visualizer modal to showcase waveforms corresponding to audio generated from TTS-converted messages.
The js directory contains an empty JS file named index.js included in the index.html file.This file will contain the code responsible for managing the client application UI.
The css directory contains a file named style.css which will be used to stylize the web app.
The audio directory contains a bell notification audio file downloaded from mixkit.co. This file will serve as the notification sound triggered when the client application detects an incoming message for your Twilio number.
Go back to your terminal and run the following command to start the server application:
Open your preferred browser, navigate to the URL http://localhost:3000/, and you should see a page similar to this:
Make sure you terminate your server application before moving to the next section.
Collect and store your credentials
In this section, you are going to retrieve the Twilio credentials that will allow you to interact with the Twilio API. Additionally, you will buy a new Twilio Phone number with SMS capabilities if you don't have one.
The .env file included in the cloned repository is where you will store all credentials.
Twilio credentials
Open a new browser tab, and log in to your Twilio Console. Once you are on your console copy the Account SID and Auth Token and store these credentials in the .env file found in the root directory of the boilerplate code:
The third and fourth required credentials are an API Key SID and API Key Secret, which can be obtained by following this guide. After obtaining both credentials, copy and store them in the .env file:
The fifth credential required is a Sync Service SID. A Sync Service is the top-level scope of all other Sync resources (Maps, Lists, Documents).
Navigate to the Twilio Sync Service page, create a new Sync Service, and copy the Sync Service SID. This credential should also be stored in the . env file once obtained:
Navigate to the Buy a Number page in your Twilio Console, and purchase a number with SMS capabilities if you don’t own one. Copy your Twilio phone number and store it in the .env file as the value for TWILIO_NUMBER:
Retrieving and displaying messages received
In this section, you will write the code that will allow your application to retrieve and display the most recent messages sent to your Twilio phone number.
Open your server.js file located in your project root directory, and add the following code below the line where you declare the port
constant on line 6:
The code, first, imports the twilio
package for interacting with the Twilio API.
Next, it creates a Twilio client object by invoking the twilio()
function and passing in the Twilio account SID and authentication token obtained from the environment variables. This client instance will be used to interact with Twilio's messaging services.
Add the following code above below the line where you set the middleware to serve the static files (app.use(express.static('public'));
):
The code defines an asynchronous function named getMessages()
which is responsible for retrieving a list of messages received using the Twilio client.
The result of this retrieval is stored in a variable named messages
and it does so by calling the list()
method on the twilioClient.messages
object, specifying the recipient number as your Twilio phone number, and limiting the result to the most recent 100 messages. The function then checks if the value stored in messages
is undefined
indicating an error retrieving the messages. If so, it initializes messages
as an empty array. Lastly, the function returns the array of messages.
Add the following code below the getMessages()
function:
Here, the code creates a route that handles incoming HTTP GET requests at the path /getMessages
. This route is designed to fetch messages sent to your Twilio number, mark them as read, and send the updated list as a response.
The route initiates by calling the asynchronous function getMessages()
, which retrieves a list of messages. Next, it iterates through each message, marking them as read by setting the read
property to true
using a for
loop. Finally, the route responds to the client with an object containing the updated list of messages
Navigate to the public directory, then to the js subdirectory, open the empty file named index.js, and add the following code to it:
The code initializes ulInboxElement
with the HTML element ID ul-inbox
, referencing an unordered list for displaying messages sent to your Twilio number.
Following that, it declares inboxMessages
without initialization; this variable will be used to store the list of displayed messages.
Add the following code below the inboxMessages
variable:
The code defines an asynchronous function named getMessages()
. This function is responsible for fetching messages from the server by making an asynchronous HTTP GET request to the /getMessages
endpoint.
Within the function, the fetch
API is utilized to send a GET request to the specified endpoint. After obtaining the response, the code extracts the JSON content using response.json()
. The extracted JSON object is destructured to retrieve the messages
property, containing the list of messages. Finally, the function returns the obtained messages array.
Add the following code below the getMessages()
function:
The code defines two functions named truncate()
and formatDate()
which are responsible for truncating and formatting strings to display messages in a concise and readable manner.
The truncate()
takes a str (string) and max (maximum length) as parameters. It shortens the input string if it exceeds the specified maximum length, appending an ellipsis (...
) if needed.
The formatDate()
function expects a date parameter, in the following string format 2024-01-05T21:34:18.000Z
. It splits the date into its date and time components using 'T' as the separator. The date is formatted by replacing hyphens with slashes, creating a day format. The time is extracted and formatted as hours and minutes. Finally, the function returns the formatted date and time.
Add the following code below the formatDate()
function:
Here, the code introduces an asynchronous function named populateMessages()
. This function is responsible for rendering and displaying messages within the user interface.
It iterates through the inboxMessages
array, extracting relevant properties such as from
, body
, dateCreated
, and read
for each message.
Within the loop, the code dynamically generates HTML elements to represent each message. It creates a list item (<li>
) for each message using document.createElement('li')
. The structure of each list item includes information like the sender, a potential new message badge, a truncated message body, a timestamp, and a Play button.
The truncate()
function is used to limit the displayed message body to 30 characters. The formatDate
function is utilized to format the date and time of the message creation.
Finally, the dynamically created HTML elements are appended to the existing ulInboxElement
.
Add the following function below the populateMessages()
:
The code declares an asynchronous function named initialize()
. This function is responsible for initializing the application or a specific module.
Within the function, the variable inboxMessages
is assigned the result of an asynchronous call to getMessages()
. The getMessages()
fetches messages sent to your Twilio number, and the result is stored in the inboxMessages
variable.
Following the retrieval of the messages, the populateMessages()
function is invoked. This function renders messages in the UI based on the fetched data.
The final line calls the initialize()
function to trigger the initialization process, ensuring that when the application is started, the inbox messages are fetched and populated in the UI.
Go back to your terminal and run the following command to start the server application:
Open another terminal tab, and run the following ngrok command:
This command above exposes your local web server, running on port 3000
, to the internet using the ngrok service.
Copy the forwarding HTTPS URL provided, paste it into the browser tab you previously opened, navigate to the pasted URL, and you should see a page similar to the following:
The page should display the most recent messages sent to your Twilio number. If your number hasn’t received any messages make sure you send one, then refresh the page before proceeding to the next section.
Using TTS to convert messages to audio
In this section, you will use the Transformers.js library to convert messages sent to your Twilio number to audio using a TTS model. Next, you will use the AudioMotion-analyzer and wavefile libraries to process and generate waveforms for the audio. Additionally, you will use the IDB-Keyval library to store and retrieve the generated audio.
Given the resource-intensive nature of executing AI models in the browser, the code responsible for this task will be housed in a dedicated Web worker file.
In the public/js subdirectory create a file named worker.js, and the following code to it:
The code imports the pipeline function
and env
module from the Transformers.js library hosted on a CDN.
The env.allowLocalModels
is set to false, to disallow the use of locally stored models and ensure the use of remote models instead.
A variable named synthesizer
is declared to store the instance of the text-to-speech synthesizer. This variable will be used to interact with the Transformers.js library for processing text and generating speech.
Add the following code below the synthesizer
variable:
This code defines an asynchronous function named getInstance()
. It initializes the text-to-speech synthesizer and takes an optional progress_callback
parameter to track initialization progress.
Within the function, it awaits the result of the pipeline()
function from Transformers.js. This function sets up a text-to-speech pipeline using the Xenova/mms-tts-eng
model designed for English synthesis. A pipeline is a unified interface for performing various natural language processing (NLP) tasks using pre-trained models.
The quantized
option is set to false
, indicating no quantization. Quantization reduces model precision for efficiency but may slightly impact accuracy.
The progress_callback
function reports progress during initialization. Once the pipeline is initialized, the synthesizer
variable holds the resulting pipeline for text-to-speech synthesis.
Add the following code below the getInstance()
function:
This code block defines an asynchronous function named initializePipeline()
. This function initializes the text-to-speech synthesizer pipeline used within the worker.
Within the function, it awaits the result of the getInstance()
function, which is responsible for setting up the text-to-speech synthesizer pipeline. The getInstance()
function takes an optional progress_callback
parameter, which is a callback function to track the initialization progress.
The getInstance()
function is called within the initializePipeline()
function, and it awaits the completion of the pipeline initialization. During this process, if progress information is received, it is posted back to the main thread using the self.postMessage()
method. This allows the client application to monitor the progress of the text-to-speech synthesizer initialization.
Add the following code below the initializePipeline()
:
The code defines an asynchronous function named convertMessageToSpeech()
. This function is responsible for converting a given text message into speech using the text-to-speech synthesizer.
The function begins by attempting to execute the text-to-speech synthesis process using the synthesizer
object. It awaits the result of this synthesis, which represents the generated speech corresponding to the input text message.
If the synthesis process is successful, the function returns the resulting speech. However, if an error occurs during the synthesis, the function catches the error, logs it to the console, and returns undefined
to indicate that the conversion process encountered an issue.
Add the following code below the convertMessageToSpeech()
function:
The code above defines a message event listener within a web worker script. This event listener responds to messages received by the worker, particularly those related to converting text messages to speech.
The event listener starts by extracting the received message from the event data and logs it to the console.
It then checks if the received message's action
property is set to ’convertMessageToSpeech’
. This conditional statement ensures that the worker responds specifically to messages requesting the conversion of a text message to speech.
Within the conditional block, it verifies whether the text-to-speech synthesizer (synthesizer
) is undefined
. If it is, the worker calls the initializePipeline()
function to set up the synthesizer. This initialization step ensures that the text-to-speech pipeline is ready for use.
Following the synthesizer initialization, the worker extracts the message body
and sid
from the received message's args property.
It proceeds to call the convertMessageToSpeech()
function, passing the message body as an argument. This function asynchronously converts the text message to speech using the pre-initialized synthesizer
.
The resulting speech, along with the message sid
, is stored in the result variable.
Finally, the worker posts a message back to the main thread. This message includes information about the status of the conversion process ('complete'
or 'error'
), the task performed ('convertMessageToSpeech'
), and the data (result
or undefined
).
In the public/js subdirectory, create a file named tts.js, and add the following code to it:
The code begins by importing the AudioMotion-analyzer and wavefile libraries from a CDN. The AudioMotion-analyzer library is used for audio visualization. It helps analyze and visualize audio data in web applications. The wavefile library is used to handle and manipulate WAV audio files.
Several constants are then declared, each representing a different HTML element obtained by its ID
. These elements include the audio visualizer modal, a play audio button inside the modal, the container for the audio visualizer, and the audio element itself.
A Bootstrap modal instance (bootStrapModal
) is created, associated with the audio visualizer modal. This instance facilitates the manipulation of the modal's appearance and behavior.
Two global variables, isLoading
and clickedPlayButtonID
, are initialized. The isLoading
variable tracks whether an audio file is currently being loaded, while clickedPlayButtonID
stores the ID
of the button that triggered the play action.
Add the following code below the clickedPlayButtonID
variable:
This code initializes a web worker by instantiating the Worker class. The worker script is specified using the URL constructor, where ./worker.js
is the relative path to the worker script file, and import.meta.url
provides the base URL of the current module.
The type "module" option indicates that the worker script should be treated as a module, allowing the use of modern ECMAScript module syntax in the worker file.
Add the following code to the bottom of your tts.js file:
The code declares a function named handlePlayButton()
. This function is responsible for toggling the visual state of the Play buttons shown next to every message received.
The function begins by retrieving the HTML button element associated with the ID stored in the variable clickedPlayButtonID
. It then toggles the isLoading
variable, which tracks whether the audio generated for the message is currently loading.
If isLoading
is true
, indicating that the content is loading, the function disables the button and updates its inner HTML to display a loading spinner.
On the other hand, if isLoading
is false
, indicating that the content has finished loading, the function enables the button and updates its inner HTML to display a play icon.
Add the following code below the handlePlayButton()
function:
The code declares and exports an async function named convertMessageToSpeech()
. This function is responsible for initiating the conversion of a given message's text content to speech. It takes two parameters, message
and btnID
.
The function begins by updating the global variable clickedPlayButtonID
with the value of btnID
. Following this, it calls the handlePlayButton()
function, which dynamically adjusts the visual state of a Play button associated with the provided btnID
.
Next, the function destructures the message parameter to extract the message body
and sid
. It then creates a data object containing an action
of 'convertMessageToSpeech'
and an args
object with the extracted sid
and body
.
Finally, the function posts this data to the web worker using the worker.postMessage()
method to initiate the text-to-speech conversion process.
Add the following code below the convertMessageToSpeech()
function:
The code defines a function named playAudio()
. This asynchronous function takes two parameters: audioData
, which represents the audio content to be played, and samplingRate
, which is the rate at which samples of the audio signal are taken per second.
Inside the function, a try-catch
block is used to handle potential errors during the audio playback process. Within the try
block, a new WaveFile
instance is created, representing a WAV audio file. The audio content (audioData
) and sampling rate (samplingRate
) are used to initialize this WAV file.
Next, a Blob is created from the WAV file's buffer, with a specified MIME type of 'audio/wav'
. The Blob is then converted into a URL using URL.createObjectURL
, and this URL is assigned to the src
attribute of the audioElement
.
The Bootstrap modal (bootStrapModal
) is displayed, indicating that audio playback is in progress. The audioElement
is then instructed to play the audio using audioElement.play()
. Additionally, the handlePlayButton()
function is called to dynamically update the visual state of the Play button.
In case of an error, the catch
block logs the error, calling handlePlayButton()
to update the Play button state. The function returns true
on successful audio playback; otherwise, it returns false
.
Add the following code below the playAudio()
:
Here the code implements onmessage
event handler for the worker object initialized earlier. This event listener is responsible for handling messages sent by the web worker to the main thread. It is triggered whenever the web worker posts a message.
Inside the event handler, the received message is stored in the message variable. The code then logs this message.
The code further checks if the task specified in the message is 'convertMessageToSpeech'
. If true
, it examines the 'status'
property within that message. If the `status`
is 'complete'
, it extracts the audio data and sampling rate from the message, then calls the playAudio()
function asynchronously, passing the audio data and sampling rate as arguments.
If the status is not 'complete'
, the handlePlayButton()
function is called to update the Play button's visual state, and an alert is displayed, notifying the user that the audio generation process has failed.
Add the following code below the onmessage
event handler:
In the code above, a function named setupAudioVisualizer()
is declared. This function is responsible for initializing and configuring an audio visualizer using the AudioMotion-analyzer library.
Inside the function, a new instance of AudioMotionAnalyzer is created and assigned to the variable audioMotion
. This instance is configured with various parameters, specifying the settings for the audio visualization. The settings include:
source
: The audio element (audioElement
) is set as the source for visualization.ansiBands
: Set tofalse
to disable ANSI bands in the visualization.showScaleX
: Set tofalse
to hide the X-axis scale.bgAlpha
: The background alpha value is set to0
(fully transparent).overlay
: Set to true to enable overlay mode.mode
: The visualization mode is set to5
.frequencyScale
: The frequency scale is set to"log"
showPeaks
: Set tofalse
to hide audio peaks.reflexRatio
: The reflex ratio is set to0.5
.reflexAlpha
: The reflex alpha value is set to1
.reflexBright
: The reflex brightness is set to1
.smoothing
: The smoothing factor is set to0.7
.gradient
: The color gradient used for visualization is set to'classic'
.
Additionally, the function overrides an existing color gradient named 'classic'
with a single color stop, where the color is set to 'white'.
Finally, the setupAudioVisualizer()
function is called to initialize the audio visualizer with the specified configurations.
At the bottom of the tts.js file, add a click event listener for the Play button located inside the modal:
This event listener ensures that when the Play button is clicked, the audio element located inside the modal begins playing the audio.
Go to the /public/js/index.js file and add the following line of code to the top of the import section to import the convertMessageToSpeech()
function:
Now, go to the populateMessages()
function located in the /public/js/index.js file, and add lines 6-13 below to the for of loop
below the line where you append a newMessageElement
to the ulInboxElement
:
The code attaches a click event listener to a Play button (btnPlayMessage-${index}
) located inside a newMessageElement
.
When the button is clicked, it triggers the convertMessageToSpeech()
function, passing the corresponding message and a unique identifier for the button. Additionally, it marks the message as read and updates the UI to reflect this change.
Restart the application by navigating back to your terminal and entering npm start
(if it is already running enter CTRL + C to end the process). Go back to the tab where the client application is open, refresh the tab, and then click on one of the messages’ displayed Play button to convert and play the message using TTS.
Saving the generated audio using the IndexedDB API
You may have observed that each time you click the Play button to listen to a particular message, there is a delay as the worker is invoked to generate the audio. To circumvent this, you will use the IDB-Keyval library for storing and retrieving the previously generated audio, eliminating the need to call the worker every time.
In the public/js subdirectory create a file named idbHelper.js, and add the following code to it:
The line above imports the get
and set
functions from the IDB-Keyval library, enabling key-value storage with IndexedDB. The get
function retrieves the value for a key, and set stores a key-value pair in IndexedDB.
Add the following code to below the get
and set
functions:
In the code above, two asynchronous functions, getAudio()
and saveAudio()
were defined. These functions manage IndexedDB interactions for audio data retrieval and storage.
The getAudio()
function, taking a key
parameter, uses the IDB-Keyval get
function to retrieve the IndexedDB value associated with the specified key. The value is then logged, and the function returns it.
The saveAudio()
function, with key
and val
parameters, uses the IDB-Keyval set
function to store audio data with the provided key
. Upon success, a success log appears, and true
is returned. On storage error, an error log appears, and false
is returned.
Go to the tts.js located in the public/js subdirectory and the following code below the line where you imported the wavefile library:
In the line above, the code imports the getAudio
and saveAudio
functions located inside the idbHelper.js
Go to the worker onmessage
event handler and add lines 7-14 before calling the playAudio()
function:
The code added attempts to save this audio data in the IndexedDB using the saveAudio()
function, passing the message sid
as the key and an object containing the audio data as the value. If the code fails to save the audio data an alert notifying the user will be displayed.
Go to the convertMessageToSpeech()
function and replace the code that appears after the sid
constant with the following:
The code added calls the getAudio()
function with the message sid
as an argument to check if there is already audio data associated with the provided sid
.
If audio data is found, the function proceeds to play the audio using the playAudio()
function, passing the audio data and sampling rate from the retrieved result.
If no audio data is found, the function constructs a data object containing an action named 'convertMessageToSpeech'
and associated arguments (sid
and body
). This object is then sent to a web worker using worker.postMessage()
to initiate the process of converting the message to speech.
Go back to the tab where the client application is open, refresh the tab, and then click on one of the messages’ displayed Play button to convert and play the message using TTS. Close the modal, then click the same message’s Play button again and observe how the audio loads faster because it is now retrieving the stored audio from IndexedDB instead of converting it again.
Handling incoming messages
In this section, first, you'll set up a server endpoint tasked with creating an access token. This token will authorize the client and server application to access the Twilio Sync API. Next, within the server, you'll instantiate a Twilio Sync client and set up an incoming message webhook. When this webhook is triggered, the code will use the Sync client instance to broadcast the incoming message to the client application, where it will be shown.
Go to your project root directory, open the server.js file, and add the following code to the bottom of the import section:
This code prepares the server to work with Twilio Sync by importing necessary modules and initializing variables related to Twilio Sync.
First, The code imports the SyncClient
class from the twilio-sync
package.
Next, the code imports the AccessToken
class and SyncGrant
class from the twilio.jwt
module. These classes will be used to generate access tokens for Twilio Sync.
The uuidv4()
function is imported from the uuid
package. This function will be used to a unique identity for every access token.
Lastly, a variable named twilioSyncClient
is declared. This variable will be used to store an instance of the Twilio Sync client once it's created.
Add the following code below the getMessages()
function:
The code above defines a function named getAccessToken()
for generating an access token for Twilio Sync.
Within it, first, an AccessToken
instance is created. The constructor for creating the AccessToken
instance takes as parameters the Twilio account SID, API key SID, secret, and a unique identity generated by calling uuidv4()
function.
Next, a SyncGrant
is formed using the SyncGrant class with the Sync service SID. This grant is added to the AccessToken via addGrant()
.
Finally, the function returns the JWT-formatted access token.
Add the following code below the getAccessToken()
function:
This code block defines a function named initializeTwilioSyncClient()
which is responsible for setting up and initializing a Twilio Sync client within the server application.
The code initializes a Twilio Sync client by obtaining an access token, creating a SyncClient instance, and assigning the instance to the global variable twilioSyncClient
.
Next, the initializeTwilioSyncClient()
function is invoked, ensuring that the Twilio Sync client is set up and ready for use when the server starts.
Add the following code below the line where the initializeTwilioSyncClient()
is invoked:
The code defines a function named updateDocument()
, responsible for updating a document within the Twilio Sync service. This function is designed to be called when your Twilio phone number receives a new message that needs to be shared among connected Twilio Sync clients.
The function calls the document()
method on the Twilio Sync client, specifying the document named 'inbox'
. It then uses the returned document object to invoke the update()
method, passing an object containing the new content (data
) and an action related to the update.
Add the following code below the /getMessages
route:
This code establishes a route at '/getToken’
to handle incoming HTTP GET requests. This route is designed to handle requests made to obtain a Twilio access token for establishing secure communication with the Twilio Sync service.
When a user accesses this endpoint, the getAccessTokenfunction()
is invoked to generate a new Twilio access token.
Next, the obtained access token is encapsulated in a JSON object with the key 'token'
and then it is sent back to the client.
Add the following code below the /getToken
route:
This code establishes a route at '/incomingMessage'
to handle incoming HTTP POST requests. The route processes incoming SMS messages and shares the processed message with other Twilio Sync clients.
First, a variable newMessage
is declared, without an initial value, and the incoming request body is stored in incomingMessage
. The function calls getMessages()
, retrieving the current messages from Twilio, and storing the list in messages
.
Next, a loop iterates over messages
, seeking a match based on the incoming message SID property. Once found, newMessage
gets assigned the value, addressing the difference in object properties between the incoming request body and what the client application expects.
Subsequently, the read
property of the found message is set to false
, indicating unread status. updateDocument()
is called with the message and a string 'newMessage'
parameters, updating the Twilio Sync ’inbox’
document to broadcast the processed incoming message.
Finally, a response is sent with a JSON object { success: true }
, signaling successful processing.
Navigate, to the public/js subdirectory, open the index.js file, and add the following code below the line where you declared the inboxMessages
variable:
Here you declared a variable named twilioSyncClient
, which will be used to store the Twilio Sync client instance.
Add the following code below the populateMessages()
function located around line 31:
Here, the code declares two functions named removeAllChildNodes()
and repopulateMessages()
.
The removeAllChildNodes()
function takes a single parameter, parent
, representing a parent HTML element. It clears all child nodes from this parent using a while
loop that iterates while the parent has a first child. Within each iteration, it removes the first child node. After execution, the specified parent element becomes empty.
The repopulateMessages()
function refreshes the displayed message list. It calls removeAllChildNodes()
passing the ulInboxElement
as an argument to clear it, then calls populateMessages()
to refill it with updated messages.
Add the following code below the repopulateMessages()
function:
The code above declares two functions named playNotificationSound()
and showNewMessageToast()
.
The playNotificationSound()
function plays a notification sound on a new message arrival. To achieve this, It creates an Audio
element, sets its source to the notification sound file in the audio directory, and then loads and plays it.
The showNewMessageToast()
function manages toast notifications for new messages. Taking a message object, it extracts the sender
and body
information, and using this information it updates the toast message content. Using Bootstrap, retrieves or creates a Bootstrap Toast instance, and shows the toast. Additionally, it calls playNotificationSound()
for an audible cue.
Add the following code below the showNewMessageToast()
function:
The code declares an async function named getAccessToken()
which is responsible for fetching the access token needed to create a Twilio Sync client.
Utilizing the Fetch API, it sends an HTTP GET request to '/getToken'
. Upon response, the function extracts the token from the response, stores it in a variable named token
, and then returns the obtained token.
Add the following code below the getAccessToken()
function:
This code defines a function named createTwilioSyncClient()
, which is responsible for connecting to Twilio Sync for real-time synchronization of incoming messages between the client and server.
Initially, it awaits the asynchronous operation of obtaining the access token through the getAccessToken()
function. Once the token is acquired, a new Twilio Sync Client instance is created using it.
The function then accesses the 'inbox'
document, attaching an event listener to its 'updated'
event.
This listener checks for the action being a 'newMessage'.
If true
, it extracts the new message content, inserts it at the beginning of inboxMessages, updates the UI with repopulateMessages()
, and displays a notification toast with showNewMessageToast()
.
In case of errors, an error message is logged to the console.
Go to the initialize()
function located at the bottom of the index.js and add the following code below the line where the `populateMessages()` function is invoked:
The code added invokes the createTwilioSyncClient()
function to create a new Twilio Sync client.
Go back to the tab where the client application is open, copy the URL, and paste it into a text editor where you can edit it. Next, add the text /incomingMessage
to the end of it so that your complete URL looks similar to the following:
Switch to the tab where you accessed your Twilio Console to navigate to Active Numbers under Phone Numbers > Manage. Click on the number you are using in this tutorial. Scroll down to Messaging. Under A MESSAGE COMES IN you will set the HTTP method to POST and assign it the ngrok https URL you combined earlier. Then, click Save to save the configuration.
Go back to the tab where the client application is open, and refresh the tab. Now send a message to your Twilio number using any method you prefer, Observe how a toast notification
appears and the bell notification plays when your Twilio number receives the message.
Conclusion
In this tutorial, you've successfully built a web application that not only retrieves and showcases the latest messages from your Twilio phone number but also converts them into audio using Text-to-Speech (TTS). Throughout the tutorial, you used the Twilio Programmable Messaging REST API for seamless message retrieval, Transformers.js for TTS functionality, the wavefile library for audio processing, and the Audiomotion-analyzer for visualizing audio waveforms. Additionally, you implemented the IDB-Keyval library for efficient audio storage and retrieval and leveraged Twilio Sync to seamlessly share incoming messages between the server and the client application.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.