No, T-Mobile is Not Going to Fine Customers for Disliked SMS

Bottom Line: A rule affecting robo-texting (which is done without a phone) was mistaken for a rule governing/punishing users of T-Mobile phones. T-Mobile has not applied any new fines to customers for their SMS content.

Update 2023 December 31 11:58PM EST

Since I’ve made this posts, there’s been a number of other statements and posts about this misunderstanding that I think are worth considering. The Associated Press published an article about this misunderstanding/misinformation

“The change only impacts third-party messaging vendors that send commercial mass messaging campaigns for other businesses,” the company wrote in a statement emailed to The Associated Press.

Associated Press

There is now a community note under the account of one of the people that accounts for many views of this item of misinformation on Twitter/X.

…Bandwidth the company the user is referring does not provide consumer service. These specific terms of service are for commercial/enterprise users of the T-Mobile network. This does not directly apply to P2P (Peer to peer) messaging.

Twitter/X Community note

There was also a post in the T-Mobile Community forums by a user that received a response from a T-Mobile Community Manager.

These changes only apply to third-party messaging vendors that send commercial mass messaging campaigns for other businesses. The vendors will be fined if the content they are sending does not meet the standards in our code of conduct, which is in place to protect consumers from illegal or illicit content and aligns to federal and state laws. 

HeavenM, T-Mobile Community Manager

Original Post

I watched misunderstanding get legs and spread pretty far during the Christmas holiday weekend. The gist of the misinformation is that T-Mobile is going to fine customers hundreds or thousands of dollars for sending text messages of which T-Mobile doesn’t improved. This misunderstanding appears to be derived from a reading of a Business Code of Conduct, but without an understanding of the terms involved or the partnerships between various companies. I’ll try to explain both in a moment.

Consumer and Non-Consumer Messages

If you have a T-Mobile phone and are sending text messages, those are consumer text messages. If you start a marketing campaign to send out mass SMS messages, create a 2FA service that sends out messages, or are using some other API access, those are non-consumer messages. A basic understanding of how consumer messages works is common, but not so for non-consumer messages. Let’s dig into that a bit more.

Non-Consumer messages are often sent through an API. These messages are sometimes labeled as A2P messages, which means “Application to Person.” The origin of these messages is not a phone. T-Mobile, AT&T, and Verizon provide the ability to post messages into their infrastructure for delivery. How do you get access to this functionality? Generally, you don’t. They don’t let everyone have access to these services. Instead, there are a number of companies with which they establish agreements and have granted access. If you want to use these services, you would establish a connection with one of these companies and they manage many of the other details of what needs to occur. Here are some names of companies that provide such services.

  • Amazon Web Services
  • Bandwidth
  • BulkSMS
  • Call Hub
  • EZTexting
  • Hey Market
  • MessageBird
  • Phone.com
  • SalesMSG
  • Textr
  • Vonage
  • Wire2Air
  • Zixflow

There are a lot of other companies. You can find a larger list here. T-Mobile also has a document explaining the difference between consumer and non-consumer messaging.

As far as a consumer knows the messages are coming from a 10 digit phone number. These phone numbers may be referred to as A2P 10DLC (Application to Person 10 Digit Long Code). When a new messaging campaign is started, the number associated with the campaign must be registered. The number being registered and associated with an entity, brand, or campaign. Unlike short code messages, if a business also needs to allow customers to call them, they have the option of setting up a voice line associated with the same phone number.

The entities and phone numbers may also be assigned a trust rating. Entities that have a higher trust rating may be granted more throughput on a carrier’s network. If an entity’s trust rating become low, their granted capacity might be lowered or their messages may be disallowed all together. Entities earn a reputation.

What Are These Rules Restricting?

In a nutshell, the rules published by T-Mobile, AT&T, and Verizon disallow text messaging campaigns for unlawful material, including material that may be lawful in some states but not others, scam messages, spam, phishing attempts and impersonation. Some carriers may specifically call out other types of material, but the same general characterization of restrictions apply. The following is what was posted by Bandwidth about the notice (Update 2024 January 2: Bandwidth.com has since restricted viewing the notice. A screenshot of the notice can be viewed here).

T-Mobile is instituting three new fees for non-compliant A2P traffic sent by non-consumers that result in a Severity-0 violation. A Sev-0, (Severity-0) represents the most harmful violation to consumers and is the highest level of escalation with which a carrier will engage with Bandwidth. This applies to all commercial, non-consumer, A2P products (SMS or MMS Short Code, Toll-Free, and 10DLC) that traverse T-Mobile’s network.

With what I’ve shared so far, you may be able to recognize this as something that is not directed at regular customers that are using T-Mobile. The false information I encountered on the change misidentifies the affected partiers as subscribers of the phone service. Let’s dig into what a Sev-0 violation is.

  • Phishing messages that appear to come from reputable companies
  • Depictions of violenge, messages engaging in harassment, defamation, deception, and fraud
  • Adult Material
  • High-Risk content that generates a lot of user complaints, such as home offers, payday loans, and gambling content.
  • Sex, Hate, Alcohol, Firearms, and Tabaco related content (SHAFT)

There is other content in this category. The above isn’t exhaustive.

Bandwidth list as having the highest fees messages related to phishing and social engineering at 2,000 USD per violation. Second, at a 1,000 USD fine is unlawful content such as controlled substances or substances not lawful in all 50 states. The lowest fine that Bandwidth mentions is 500 USD is for violations of SHAFT or messages that don’t follow state or federal regulations.

Overall, this looks to be a move that may motivate A2P partners to make more efforts to filter out certain type of content that is at least generally annoying if not worst.

What About the Other Carriers?

In the USA there are three nation-wide carriers. AT&T, T-Mobile, and Verizon offer service across the nation. I don’t know if Verizon or AT&T have fines associated with violations, but they do have a code of conduct for A2P providers. If you’d like to read their code of conduct documentations, you can find them here.

There’s a lot of overlap in their rules. This may come as no surprise, especially to those familiar with CTIA. CTIA is a wireless trade organization representing carriers in the USA, supplies, and manufacturers of wireless products. They have been around since the mid-1980s. Current members of CTIA include AT&T, T-Mobile, Verizon, and US Cellular. You can find a list of members here.

There is a lot more that could be said about how these messaging services work. I may detail it further one day. But for now, the main take-away is that there’s a popular misconception about the changes that T-Mobile is said to be implementing that are recognized as a mistaken interpretation once one has a casual familiarity with some of the terms involved.


Posts may contain products with affiliate links. When you make purchases using these links, we receive a small commission at no extra cost to you. Thank you for your support.

Mastodon: @j2inet@masto.ai
Instagram: @j2inet
Facebook: @j2inet
YouTube: @j2inet
Telegram: j2inet
Twitter: @j2inet

Updating Christmas Gifts After they Are Sent

Christmas is around the corner. Among the items of interest this year is the Analogue Pocket. The Pocket is an FPGA based device that can hardware emulate a lot of older game consoles along with having some games of its own. I’m getting one prepared for someone else. But I also need to send the device soon to ensure that it arrives at its destination before Christmas. This creates a conflict with getting more games loaded while also shipping it on time. No worries, I can satisfy both if I send the device with something to upload the content.

This is done by a lot of physical game releases, when there is a zero-day “patch” for a game or when the disc is only a license for the game, but the actual game and it’s content are only available online. I’ll be shipping the memory card for the device with an call to action to run the “game installer” on the memory card. After the card is mailed, I can take care of preparing the actual image. The game installer will reach out to my website to find a list of files to download to the memory card, zip files to decompress, or folders to create.

Safety

Though I’m the only one that will be making payloads for my downloader to run, I still imagined some problem scenarios that I wanted to make impossible or more difficult. What if someone were to modify the download so that it were to target writing files to a system directory or some other location? I don’t want this to happen. I’ve made my downloader so that it can only write to the folder in which it lives and to subfolders. The characters that are needed to get to some parent level or to some other drive, if present in the download list, will intentionally cause the application to crash.

Describing an Asset

I started with describing the information that I would need to download an asset. An asset could be a file, a folder, or a zip file. I’ve got an enumeration for gflagging these types.

    public enum PayloadType
    {
        File,
        Folder,
        ZipFile,
    }

Each asset of this type (which I will call a “Payload” from hereon) can be described with the following structure.

    public class PayloadInformation
    {
        [JsonPropertyName("payloadType")]
        [JsonConverter(typeof(JsonStringEnumConverter))]
        public PayloadType PayloadType { get; set; } = PayloadType.File;

        [JsonPropertyName("fileURL")]
        public string FileURL { get; set; } = "";

        [JsonPropertyName("targetPath")]
        public string TargetPath { get; set; } = "";

    }

For files and zip archives, the FileURL property contains the URL to the source. The TargetPath property contains a relative path to where this payload item should be downloaded or unzipped to. A download set could have multiple assets. I broke up the files for the the device that I was sending into several Zip files. Sorry, but in the interest of not inundating my site with several people trying this out, I’m not exposing the actual URLs for the assets here. The application will be grabbing a collection of these PayloadInformation items.

    public class PayloadInformationList: List<PayloadInformation>
    {
        public PayloadInformationList() { }
    }

The list of assets is placed in a JSON file and made available on a web server.

[
  {
    "payloadType": "ZipFile",
    "fileURL": "https://myserver.com/Pocket.zip",
    "targetPath": "",
    "versionNumber": "0"
  },


  {
    "payloadType": "ZipFile",
    "fileURL": "https://myserver.com/Assets_1.zip",
    "targetPath": "Assets",
    "versionNumber": "0"
  },

  {
    "payloadType": "ZipFile",
    "fileURL": "https://myserver.com/Assets_2.zip",
    "targetPath": "Assets",
    "versionNumber": "0"
  },

  {
    "payloadType": "ZipFile",
    "fileURL": "https://myserver.com/Assets_3.zip",
    "targetPath": "Assets",
    "versionNumber": "0"
  },

  {
    "payloadType": "ZipFile",
    "fileURL": "https://myserver.com/Assets_4.zip",
    "targetPath": "Assets",
    "versionNumber": "0"
  },
  {
    "payloadType": "Folder",
    "targetPath": "Memories/Save States",
    "versionNumber": "1"
  },
  {
    "payloadType": "Folder",
    "targetPath": "Assets",
    "versionNumber": "1"
  }
]

I might use some form of this again someday. So I’ve placed the initial URL from which the download list is retrieved in the Application Settings. In the compiled application, the application settings are saved in a JSON file that can be altered with any text editor.

About the Interface

The user interface for this application is using WPF. I grabbed a set of base classes that I often use with WPF applications. It made this using a build of Visual Studio that was just released a month ago that contains significant updates. I found that my base class nolonger works as expected under this new version of Visual Studio. That’s something I will have to tackle another day, as I think that there is a change in the relationship between Linq Expressions and Member Expressions. For now, I just used a subset of the functionality that the classes offerd. Most of the work done by the application can be found in MainViewModel.cs.

To retrieve the list of assets, I have a method named GetPayload() that downloads the JSON containing the list of files and deserializing it. Though I would usually use JSON.Net for serialization needs, I used the System.Text.Json.Serializer for my needs. Here, I also check the paths for characters indicating an attempt to go outside of the application’s root directory and thrown an exception of this occurs.

async Task<List<PayloadInformation>> GetPayloadList()
{
    HttpClient client = new HttpClient();
    var response = await client.GetAsync(DownloadUrl);
    var stringContent = await response.Content.ReadAsStringAsync();
    var payloadList = JsonSerializer.Deserialize<List<PayloadInformation>>(stringContent);
    payloadList.ForEach(p =>
    {
        if (!String.IsNullOrEmpty(p.TargetPath))
        {
            if (p.TargetPath.Contains("..") || p.TargetPath.Contains(":") ||
                p.TargetPath.StartsWith("\\") || p.TargetPath.StartsWith("/")
            )
           {
               throw new Exception("Invalid Target Path");
            }
        }
    });
    return payloadList;
}

Within MainViewModel::DownloadRoutine() (which runs on a different thread) I step through the payload descriptions one at a time and take action for each one. For folder items, the application just creates the folder (and parent folders if needed). For files, the file is downloaded from the web source to a temporary file on the computer. After it is completely downloaded, it is moved to the final location. This reduces the chance of there being a partially downloaded file on the memory card. The process performed for Zip files is a variation of what is done for files. The zip file is downloaded to a temporary location, and then it is decompressed from that temporary location to its target folder.

while (_downloadQueue.Count > 0)
{
    Phase = "Downloading...";
    var payload = _downloadQueue.Dequeue();
    DownloadProgress = 0;
    CurrentPayload = payload;
    switch (payload.PayloadType)
    {
        case PayloadType.File:
            {
                Phase="Downloading";
                var response = client.GetAsync(payload.FileURL).Result;
                var content = response.Content.ReadAsByteArrayAsync().Result;
                var tempFilePath = Path.Combine(TempFolder, payload.TargetPath);
                var fileName = Path.GetFileName(payload.FileURL);
                File.WriteAllBytes(tempFilePath, content);
                File.Move(tempFilePath, payload.TargetPath, true);
            }
            break;
        case PayloadType.Folder:
            {
                Phase = "Creating Directory";
                var directoryName = payload.TargetPath.Replace('/', Path.DirectorySeparatorChar);
                var directoryInfo = new DirectoryInfo(directoryName);
                if (!directoryInfo.Exists)
                {
                    directoryInfo.Create();
                }
            }
            break;
        case PayloadType.ZipFile:
            {
                WebClient webClient = new WebClient();
                webClient.DownloadProgressChanged += DownloadProgressChanged;
                webClient.DownloadFileCompleted += WebClient_DownloadFileCompleted;
                var tempFilePath = Path.Combine(TempFolder, Path.GetTempFileName()) + ".zip";
                var fileName = Path.GetFileName(payload.FileURL);
                var directoryName = payload.TargetPath.Replace('/', Path.DirectorySeparatorChar);

                if (String.IsNullOrEmpty(directoryName))
                {
                    directoryName = ".";
                }
                var directoryInfo = new DirectoryInfo(directoryName);
                if (!directoryInfo.Exists)
                {
                    directoryInfo.Create();
                }
                webClient.DownloadFileAsync(new Uri(payload.FileURL), tempFilePath);
                _downloadCompleteWait.WaitOne();
                Phase = "Decompressing";
                System.IO.Compression.ZipFile.ExtractToDirectory(tempFilePath, directoryInfo.FullName,true);

            }
            break;
        default:
            break;
    }
}

Showing Progress

The download process can take a while. I thought it would be important to make known that the process was progressing. The primary item of feedback shown is a progress bar. As long as it is growing in size, it’s known that data is flowing. I used the WebClient::DownloadProgressChanged event to get updates on how much of a file has been downloaded and updating the progress bar accordingly.

void DownloadProgressChanged(Object sender, DownloadProgressChangedEventArgs e)
{
    // Displays the operation identifier, and the transfer progress.
    System.Diagnostics.Debug.WriteLine("{0}    downloaded {1} of {2} bytes. {3} % complete...", 
                        (string)e.UserState, e.BytesReceived,e.TotalBytesToReceive,e.ProgressPercentage);
    DownloadProgress = e.ProgressPercentage;
}

Handling Errors

Theres a good bit of error handling that is missing from this code. I made the decision to do this because of time. Ideally, the program would ensure that it has a connection to the server with the source files. This is different than checking whether there is an Internet connection. The computer having an Internet connection doesn’t imply that it has access to the files. Nor does having access to the files imply generally having access to the Internet. Having used a lot of restricted networks, I’m of the position that just making sure there is an Internet connection too possibly not be sufficient.

It is also possible for a download to be disrupted for a variety of reasons. In addition to detecting this, implementing download resumption would minimize the impact of such occurrences.

If I come back to this application again, I might first problem each of the reasources with an HTTP HEAD requests to see whether they are available. Such a requests would also make known the sizes of the files, which could be used to implement a progress bar for the total progress. Slow downloads, though not an error condition, could be interpreted as an error. Sufficiently informing the user of what’s going on can help prevent it from being thought of as such.

The Code

If you want to grab the code for this and use it for your own purposes, you can find it on GitHub.

https://github.com/j2inet/filedownloader


Posts may contain products with affiliate links. When you make purchases using these links, we receive a small commission at no extra cost to you. Thank you for your support.

Mastodon: @j2inet@masto.ai
Instagram: @j2inet
Facebook: @j2inet
YouTube: @j2inet
Telegram: j2inet
Twitter: @j2inet