Guarantee SMS and MMS delivery order in C# .NET

March 16, 2023
Written by
Reviewed by

Guarantee SMS and MMS delivery order in C# .NET

You can quickly send single SMS and MMS using Twilio, but when you want to send multiple SMS/MMS to a recipient, Twilio cannot guarantee that the messages will be delivered in the order they were created.

In this tutorial, you'll learn how to guarantee the delivery order of your messages, by creating an application that sends the 4 different parts of the following meme as separate MMS's:

Gru meme template, but instead of Gru there"s a German Shepard. The text on the 4 different canvases says the following: "My owner throws the ball", "I go after it and return it to them", "They never actually threw the ball and are still holding on to it.", "They never actually threw the ball and are still holding on to it."
 

Prerequisites

Here’s what you will need to follow along:

You can find the source code for this tutorial on GitHub. Use it if you run into problems, or submit an issue if you need help.

Create and set up your C# console project

Open your preferred shell and run the following commands to create a new VB.NET console project:

dotnet new console -o SendMultipleSmsInOrder
cd SendMultipleSmsInOrder

You'll need to store sensitive secrets to authenticate with the Twilio API. You'll use the .NET Secret Manager, aka user secrets, to store your secrets.

Start by initializing user secrets for your project using this command:

dotnet user-secrets init

Twilio Account Info showing the Account SID and Auth Token.

Then, go back to the Twilio Console and find your Account SID and Auth Token in the Account Info section. Use them to replace the respective placeholders in the commands below before running them; they store the Account SID and Auth Token as user secrets.

dotnet user-secrets set "Twilio:AccountSid" "[YOUR_ACCOUNT_SID]"
dotnet user-secrets set "Twilio:AuthToken" "[YOUR_AUTH_TOKEN]"

Next, configure the Twilio phone number you want to send texts from and the phone number you want to send texts to:

dotnet user-secrets set "FromPhoneNumber" "[YOUR_TWILIO_NUMBER]"
dotnet user-secrets set "ToPhoneNumber" "[RECIPIENT_PHONE_NUMBER]"

To load these secrets into your .NET application, you'll need to add the user secrets configuration package. Add it by running the following command:

dotnet add package Microsoft.Extensions.Configuration.UserSecrets

Next, add the Twilio SDK for .NET to your project:

dotnet add package Twilio

Now, open the project in your preferred IDE and update the Program.cs file with the following code:

using Microsoft.Extensions.Configuration;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;

var configuration = new ConfigurationBuilder()
    .AddUserSecrets<Program>()
    .Build();

var from = configuration["FromPhoneNumber"];
var to = configuration["ToPhoneNumber"];

TwilioClient.Init(
    configuration["Twilio:AccountSid"],
    configuration["Twilio:AuthToken"]
);

var images = new[]
{
    "https://raw.githubusercontent.com/Swimburger/SendMultipleSmsInOrder/main/images/DogMeme1.png",
    "https://raw.githubusercontent.com/Swimburger/SendMultipleSmsInOrder/main/images/DogMeme2.png",
    "https://raw.githubusercontent.com/Swimburger/SendMultipleSmsInOrder/main/images/DogMeme3.png",
    "https://raw.githubusercontent.com/Swimburger/SendMultipleSmsInOrder/main/images/DogMeme4.png",
};

for (var index = 0; index < images.Length; index++)
{
    var image = images[index];
    var messageResource = await MessageResource.CreateAsync(
        to: new PhoneNumber(to),
        from: new PhoneNumber(from),
        mediaUrl: new List<Uri>
        {
            new(image)
        }
    );
    
    Console.WriteLine($"Status: {messageResource.Status}");
}

The program:

  • Loads the user secrets for the project and puts them into the configuration variable
  • Retrieves the Twilio Account SID and Auth Token and initializes the TwilioClient.
  • Sends 4 images using 4 separate MMS.

Try it out by running the following command:

dotnet run

The output of this looks like this:

Status: queued
Status: queued
Status: queued
Status: queued

When you create a message resource in Twilio's API, Twilio will not send the message during that API call, and then respond. Instead, it'll create the message resource and queue it for delivery, hence the status is queued.

Open your phone to see the resulting MMS's arrive at your phone. It should look like this:

Messaging conversation where 4 images are sent from the previous meme, but they are out of order.

All the images have been received successfully, but if you pay close attention, you'll notice that they are completely out of order. While Twilio will send the messages in the order that you’ve queued them, the messages are delivered individually with no association to each other. The order of delivery depends on the carrier and the receiving mobile device.

This isn't just the case with MMS messages, but also with normal SMS. However, sending MMS takes significantly longer, which is why it is much easier to demonstrate this problem with MMS.

Let's take a look at how you can solve this problem.

Add delays between messages

The easiest way to "solve" this is by adding delays between sending each message.

Update the previous for-loop with the following for-loop:


for (var index = 0; index < images.Length; index++)
{
    var image = images[index];
    var messageResource = await MessageResource.CreateAsync(
        to: new PhoneNumber(to),
        from: new PhoneNumber(from),
        mediaUrl: new List<Uri>
        {
            new(image)
        }
    );
    
    Console.WriteLine($"Status: {messageResource.Status}");

    // if not the last image
    if (index != images.Length - 1)
    {
        await Task.Delay(TimeSpan.FromSeconds(10));
    }
}

Then run the application again:

dotnet run

You'll notice that there's a 10-second delay between each image and the delivery order should be preserved, in most cases.

For SMS, a one-second delay should give you a similar result.

While this works, it's a hacky solution where you're guessing how much delay you should add. You're not really guaranteeing the delivery will be in order, rather you're lowering the chances of them being delivered out of order.

Poll the message status

Rather than guessing how much delay you should add to minimize the problem, you can query the status of the message over and over until it is delivered. Checking whether data has changed on an interval is also called polling.

Update the for-loop again:


for (var index = 0; index < images.Length; index++)
{
    var image = images[index];
    var messageResource = await MessageResource.CreateAsync(
        to: new PhoneNumber(to),
        from: new PhoneNumber(from),
        mediaUrl: new List<Uri>
        {
            new(image)
        }
    );
    Console.WriteLine($"Status: {messageResource.Status}");

    // if not the last image
    if (index != images.Length - 1)
    {
        const int amountOfPollingAttempts = 20;
        var pollingInterval = TimeSpan.FromSeconds(1);
        for (var i = 0; i < amountOfPollingAttempts; i++)
        {
            messageResource = await MessageResource.FetchAsync(messageResource.Sid);
            if (messageResource.Status == MessageResource.StatusEnum.Delivered)
            {
                break;
            }

            await Task.Delay(pollingInterval);
        }
    }
}

In the above code, amountOfPollingAttempts will determine how many times you will check whether the status has changed to delivered, and pollingInterval is the time between each check. In the above example, the status will be checked up to 20 times, with a one-second delay between each check.

MMS delivery is slower depending on the size of the media. When I was testing the application above, it took about 8 polling attempts until the MMS was delivered. SMS on the other hand is usually delivered within a second, when I tested it. Results may vary depending on location and carrier.

Once the status changes to delivered, the inner for-loop is stopped so that the outer for-loop can move to the next iteration and send the next message. If the status never becomes delivered in the 20 times it is checked, the outer for-loop will still continue. You may want to change this behavior to log a warning and to stop the outer for-loop as well.

Run the application again to try out the change:

dotnet run

Just like before, you should receive the images in the correct order.

You could further enhance this code by refactoring the polling code into a method. Here's the update for-loop preceded by a local function that takes care of the polling:


async Task<bool> WaitForDelivery(string messageSid, int amountOfPollingAttempts, TimeSpan pollingInterval)
{
    for (var i = 0; i < amountOfPollingAttempts; i++)
    {
        var message = await MessageResource.FetchAsync(messageSid).ConfigureAwait(false);
        if (message.Status == MessageResource.StatusEnum.Delivered)
        {
            return true;
        }

        await Task.Delay(pollingInterval).ConfigureAwait(false);
    }

    return false;
}

for (var index = 0; index < images.Length; index++)
{
    var image = images[index];
    var messageResource = await MessageResource.CreateAsync(
        to: new PhoneNumber(to),
        from: new PhoneNumber(from),
        mediaUrl: new List<Uri>
        {
            new(image)
        }
    );
    Console.WriteLine($"Status: {messageResource.Status}");

    // if not the last image
    if (index != images.Length - 1)
    {
        var wasDelivered = await WaitForDelivery(
            messageSid: messageResource.Sid, 
            amountOfPollingAttempts: 20,
            pollingInterval: TimeSpan.FromSeconds(1)
        );
        if (wasDelivered == false)
        {
            Console.WriteLine("Message wasn't delivered in time.");
            break;
        }
    }
}

The WaitForDelivery method performs the same polling logic as before, but returns true if delivered and false if not delivered within the expected time. If false a warning is logged to the console and the for-loop is stopped.

Use the status callback webhook

When you create a message resource, you can set the statusCallback parameter. This parameter takes a URL and Twilio will send an HTTP request with the message details whenever the status changes. You can use this instead of polling, to get the delivery notification in real-time.

It would be possible to use a web server to receive these statusCallback requests, and connect it to your console application to get the status changes in real-time. There would be some other ways to accomplish this, but all these solutions are a lot more complicated and won't be covered in this tutorial. I'd recommend keeping it simple and stick with delays or polling.

If you are interested in the statusCallback webhook, check out the docs for the Message Resource.

Next steps

Great job! You now know how to send multiple SMS and MMS messages while preserving their order. Want to put this new technique to use? Check out this tutorial on building an SMS chatbot using OpenAI's ChatGPT.

We can't wait to see what you build. Let us know!

Niels Swimberghe is a Belgian American software engineer and technical content creator at Twilio. Get in touch with Niels on Twitter @RealSwimburger and follow Niels’ personal blog on .NET, Azure, and web development at swimburger.net.