Build a Voicemail Inbox using Twilio Voice and Blazor (Part 2)
Time to read: 8 minutes
In Part 1 of this series, you implemented a voicemail service that you can manage by calling your own Twilio number. You could then listen to the voice messages and choose to save or delete them. In this article, you will improve your voicemail service by adding a GUI so that you can carry out the same operations via the web and also see more information about the call, such as the caller number, duration and even the transcript of the message. If this sounds interesting to you, let's get started!
Prerequisites
You'll need the following things in this tutorial:
- A free Twilio account
- A Twilio phone number
- An OS that supports .NET (Windows/macOS/Linux)
- .NET 7.0 SDK (newer and older versions may work too)
- A code editor or IDE (Recommended: Visual Studio Code with the C# plugin, Visual Studio, or JetBrains Rider)
- ngrok (A free ngrok account is sufficient for this tutorial)
- Git CLI
- Experience with ASP.NET Core, the Twilio voice webhook, and TwiML
Project Overview
The project will start where Part 1 ended. The older version relies on filenames to keep the state of the recordings (new vs saved). In this version, you will introduce a SQLite database to store the metadata such as caller number, call duration, etc.
Project setup
The easiest way to set up the starter project is by cloning the sample GitHub repository.
Open a terminal, change to the directory you want to download the project (on the starter-project branch) and run the following command:
Before making any changes, ensure the current version is in working order.
Run the following command to start tunneling:
Update the Twilio incoming call webhook URL with the Forwarding URL assigned to you by ngrok, and add the /IncomingCall path. Leave the ngrok tunnel running and open a separate shell for the upcoming commands.
In a new shell, change directories to the web API project.
To test leaving a voicemail, remove your phone number from the Voicemail:Owners
array in
appsettings.json and run the application with the following command:
Call your Twilio phone number, and you should be able to record a message. The saved message should appear under the wwwroot/Voicemails directory.
Update the appsettings.json by adding your number to the owner's list (using E. 164 formatting) and call your Twilio phone number again.
This time you should be greeted with a message telling you have one new message and no saved messages, followed by the recorded message.
If your project works so far, you're ready to move on to the next section to refactor and improve. If not, please refer to the original article and make sure your setup works.
Use EF Core and a SQLite database to store metadata
In the first version, you used the filename to store very limited metadata about the recording: Call status, which can be "New" or "Saved". This was used to order the recordings to play the new ones first.
In this version, you will use a local SQLite database instead. First, start by adding the EF Core SQLite NuGet package to your API project:
Create a new directory called Data and add a new C# file under it called RecordingContext.cs. Update the contents as below:
The context class you created above refers to an entity class called Recording
which you will use to save the recording metadata. Next, create a new file under the Data directory called Recording.cs and update it as shown below:
SQLite is a file-based SQL database engine. EF Core is going to create the database for you, as you will see later, but first, you have to specify the path for the database file.
Open Program.cs file and add the highlighted lines as shown below:
Also, add the using statements at the top:
You can use any path or file name you like. The above example uses the local data path and uses "recordings.db" as the filename.
You can now inject the RecordingContext
class wherever you want to carry out database operations. Alternatively, you can create a separate class to encapsulate all the database operations. This way your business logic doesn't become tied to the implementation. To achieve this, create an interface under the Data directory called IRecordingRepository.cs with the following code:
This interface defines all the database operations you will need:
NewRecording
: Creates and inserts a new Recording object when a new message comes in.UpdateCallerAndTranscription
: It takes a while to get the transcription, and it's handled in a different endpoint. When Twilio calls your endpoint with the transcript info, this method is called to update the record. It also updates the caller number as it's not present in the recording status callback.ChangeStatusToSaved
: Updates the status column to Saved for the specified recording.Delete
: Removes the recording metadata from the database.GetAll
: Returns all recordings in the database.
To implement this interface, create a new class called RecordingRepository
under the Data directory:
The repository class encapsulates RecordingContext
and carries out the DB operations via EF Core.
You also have to register it to the IoC container by updating Program.cs as shown below:
Finally, it's time to create the database. Run the following commands to scaffold the migrations and create the database:
You should now see a directory inside your project called Migrations and the recordings.db file in your local data path.
Refactor Controllers and Services
Now that you have a database layer, it's time to refactor the controllers to make use of it.
Open RecordController.cs under the Controllers directory and set up IRecordingRepository
as shown below:
Also add this required using statement:
Replace the RecordingStatus
action with the code below:
The previous version only downloaded the file. Now, in addition to that, you also insert the metadata into the database.
Another change in the controller needs to be done in the Index
method. You will instruct Twilio to transcribe the call and send you the transcription by updating the Record
call as below:
And add the method to handle the transcription callback:
Update your settings and remove your phone from the owner list to test recording a new message again. Repeat the previous test. This time, you should also see a new record in the recordings.db database.
After you've saved your message, you should see a new call to your API:
The record is first inserted in the /Record/RecordingStatus endpoint. Then updated with the caller number and the call transcription in the /Record/TranscribeCallback endpoint.
Your recording row should look something like this in your database:
Next step is refactoring the DirectoryController
class to use the database. Like RecordController
, you need to inject the repository to handle the database operations.
Open DirectoryController.cs and set up IRecordingRepository
as shown below:
Then, add the required using statement:
Current version gets the recordings from the FileService
. In this version, you will get that data from the database.
Update the first 3 lines of the Index
method as shown below:
Another change in this method is at the bottom, where you filter the recordings. Again, you will now use the RecordingRepository
instead of the FileService
. Replace the var allMessages = _fileService.GetRecordingSids;
call with the code below:
Final modification in this controller will be in the Gather
method. Update the actions as shown below:
This way, in addition to managing the actual MP3 files, you keep the database accurate.
For this update to work, you have to convert the Gather
method to async
by making the following change in the method signature:
Due to all these changes, the functionality of the FileService
has also changed. You don't have to prefix the file name with its status anymore. Therefore, you don't need a save method. Update the current service with the simplified version as shown below:
Leave another message for yourself and test the existing functionality by adding your number to the owner list and calling back. Once you've confirmed everything is still working as expected, move on to the next section to implement a new controller for your future front-end.
Implement the new controller
Currently, you have a voice-based user interface where you're prompted with options when you call your own number. The GUI you will implement will need to have a new API endpoint that it can talk to. To achieve this, create a new controller under the Controllers directory called RecordingManagementController
and update its code as shown below:
As you can see, the goal here is to have the same functionality via the GUI.
Now that you have your API ready for the front-end, proceed to implement it.
Implement the front-end
In the terminal, navigate to the solution level and run the following command:
This should create a new Blazor WebAssembly project and add it to your solution.
The Blazor application sets up and HTTP client to resolve HTTP requests to its own app URL, but you'll need to make HTTP requests to the API project running on a separate URL. The web API runs on http://localhost:5096, so update your VoicemailDirectory.Console/Program.cs as shown below to reflect this:
To get rid of the default navigation and sample pages, update the MainLayout.razor as shown below:
Also remove the following files to keep things clean:
- Shared/SurveyPrompt.razor
- Shared/NavMenu.razor
- Shared/NavMenu.razor.cs
- Pages/Counter.razor
- Pages/FetchData.razor
Replace the contents of Index.razor with the code below:
This page calls the newly created API endpoints to get the list of Recording objects and save/delete operations.
Create a new class called Recording
inside the Blazor project as well, and paste the same code as the API:
For this setup to work, you also need to update the CORS policy of your API. Even though both applications run locally, they run on different ports and thus on different origins. For security reasons, browsers don't allow one origin to send HTTP requests to other origins. You can provide Cross-Origin Resource Sharing (CORS) headers from your API project to tell the browser which origins are allowed to send HTTP requests to the API project. So in order for Blazor to call the API, you need to enable a permissive CORS policy in the API project.
Open the Program.cs file in the API project and the highlighted lines as shown below:
And the following line to enable the policy:
Then, restart your API for your changes to take effect.
So the final step is to run the front-end test the functionality using the Blazor WebAssembly front-end.
Test via the front-end
While your API is still running, open another terminal inside the console application and run it with the following command:
Open a browser and go to your application at http://localhost:{YOUR APPLICATION'S PORT}.
It should look like this:
If you have lots of messages, you can play them very quickly as the play button for all of them will be directly accessible to you, whereas if you tried to achieve the same via the voice UI, you would have to go through all the messages one-by-one. Also, you can see the transcriptions, so you may not even want to play the messages in the first place.
Conclusion
In this tutorial, you implemented using a SQLite database with EF Core, and also implemented your Blazor WebAssembly front-end to add more functionality to your voicemail service. I hope you enjoyed following this tutorial as much as I enjoyed writing it.
If you'd like to keep learning, I recommend taking a look at these articles:
- How to Integrate Amazon Cognito Authentication with Hosted UI in ASP.NET Core
- Build a ChatGPT SMS bot with Azure OpenAI Service and ASP.NET Core
- Transcribe phone calls in real time with Twilio, Vosk, and ASP.NET Core
Volkan Paksoy is a software developer with more than 15 years of experience, focusing mainly on C# and AWS. He’s a home lab and self-hosting fan who loves to spend his personal time developing hobby projects with Raspberry Pi, Arduino, LEGO and everything in-between. You can follow his personal blogs on software development at devpower.co.uk and cloudinternals.net.
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.