Skip to main content

📌 SMS Status Callbacks for Delivery Tracking


This script utilizes SMS status callbacks to show a simple delivery status tracker that could begin running before a message campaign goes out and end when a message campaign ends. This tracker will log every message status event to the console and keep a record of any message failures. When the message campaign is complete and the app is ended, all the failures along with relevant information will be downloaded to CSV for later investigation.

You can learn more about SMS callbacks, all of the possible parameters you can use, and how to set them up in our status callback mega guide!

You can read the guide below, or use our recipe to quickly copy the code!

🦉 Open Recipe: SMS Status Callbacks


What do I need to run this code?

For this code to work, you will need to have the following libraries installed (click on their names to get installation instructions):

How to Run Snippet?

To run the application, execute export then run flask run.

Code Walkthrough

Load the necessary libraries

We will start by importing the necessary resources.

from flask import Flask, request
import logging
import pandas as pd
from import Client as signalwire_client
import atexit

app = Flask(__name__)

Create an array to store message data

undeliveredArray will be used to store all undelivered or failed messages as they come in.

# create an empty array to keep track of all of our undelivered 
# or failed messages during the time this app is run
undeliveredArray = []

Prepare function to run when the app terminates

When the messaging campaign is over and the Flask app is closed, the whole table will be exported to CSV so that failed/undelivered messages can be easily investigated.

# define actions to take when flask app is closed 
# export dataframe of all failed or undelivered
# messages to CSV with added detail
def onExitApp(dataframe):
dataframe.to_csv('failedAndUndeliveredMessages.csv', index=False, encoding='utf-8')
print('SMS Callback App Exited')

Instantiate the SignalWire Client

# authenticate the SignalWire client
client = signalwire_client("ProjectID",

Handle message status callbacks

While the app is running, it will log the status change event of every single message to the console with the following information: MessageStatus, MessageSid, and ErrorCode. This will happen for every outbound message in the same project that includes this script as the StatusCallback URL.

When a message returns a status of failed or undelivered, it will be added to a table that keeps track of all unsuccessful messages along with their MessageSid, MessageStatus, ErrorCode, ErrorMessage, DateSent, To, From, and Body. After every failed/undelivered message, this table will be printed so the updated list can be seen.

# define route for SMS status callbacks to be posted to 
@app.route("/smsErrorTracker", methods=['POST'])
def newest_message():
# get message sid, message status, and error code (if it exists) from callback parameters
# if they don't exist, set to None
message_sid = request.values.get('MessageSid', None)
message_status = request.values.get('MessageStatus', None)
error_code = request.values.get('ErrorCode', None)

# log every message that comes in to console'SID: {}, Status: {}, ErrorCode: {}'.format(message_sid, message_status, error_code))

# if the message is undelivered or failed, use message SID to fetch additional data
# about the failed message
if (message_status == "undelivered" or message_status == "failed"):
message = client.messages(message_sid).fetch()

# add identifying data from newest message to undelivered array
undeliveredArray.append([message_sid, message_status, error_code, message.error_message, message.date_sent,, message.from_, message.body])
# insert array into dataframe with columns for easier reading
df = pd.DataFrame(undeliveredArray, columns=('Message Sid', 'Message Status', 'Error Code', 'Error Message', 'Date Sent', 'To', 'From', 'Body'))
# print dataframe to string for nicer formatting and set dataframe to our parameter in function for handling app exit
atexit.register(onExitApp, dataframe=df)

# return 200OK
return ('', 200)

if __name__ == "__main__":


What do I need to run this code?

We will need the following libraries (click their names to get instructions on how to install them):

How to Run Snippet?

If you save this code snippet in a file called SMSStatusCallbacks.js, for example, you then need to run:
node SMSStatusCallbacks.js.

Code Walkthrough

Load the necessary libraries and start the server

const dfd = require("danfojs");
const fs = require("fs");
const express = require("express");
const { RestClient } = require('@signalwire/compatibility-api')

var app = express();

app.listen(3000, () => {
console.log("Server running on port 3000");

Create an array to store message data

undelivered_messages will be used to store all undelivered or failed messages as they come in.

let undelivered_messages = [];

Prepare function to run when the app exits

When we stop the application, and only when we stop it, we create a DataFrame based on the undelivered_messages (populated as new undelivered or failed messages come in). We also print it to the terminal and export it to a CSV file.


process.on('SIGINT', function () {
let undelivered_message_data = new dfd.DataFrame(undelivered_messages, {
columns: ['Message SID', 'Message Status', 'Error Code', 'Error Message', 'Date Sent', 'From', 'To', 'Body'],
config: {
tableDisplayConfig: {
columns: [
{ width: 1 },
{ width: 36 },
{ width: 14 },
{ width: 10},
{ width: 16 },
{ width: 16 },
{ width: 14 },
{ width: 14},
{ width: 36}

fs.writeFileSync("UndeliveredMessages.csv", dfd.toCSV(undelivered_message_data));

Instantiate the SignalWire REST Client

Here we load the REST Client, but we need to make sure to update the credentials with your own Project ID, Access Token, and Space URL.

const client = RestClient(
{ signalwireSpaceUrl: "" })

Handle all incoming message status callbacks

Here we expose the /smsErrorTracker route. As new message status callbacks come in, we print the Message SID, Status, and Error Code to the terminal. Finally, if the message status is undelivered or failed we add it to
the undelivered_messages array."/smsErrorTracker", (req, res, next) => {
let message_sid = req.body.MessageSid;
let message_status = req.body.MessageStatus;
let error_code = req.body.ErrorCode;

console.log("SID: ", message_sid);
console.log("Status: ", message_status);
console.log("Error Code: ", error_code);

if (message_status == "undelivered" || message_status == "failed") {
try {
client.messages(message_sid).fetch().then((message) => {
} catch (error) {
console.log("ERROR: " + error)

Wrap up

If something ever goes wrong with your messaging traffic, using this snippet will keep you on top of things very quickly, and you will be able to investigate the problem or ask our Support team for help with the Message SIDs in question!

Sign Up Here

If you would like to test this example out, you can create a SignalWire account and space here.

Please feel free to reach out to us on our Community Slack or create a Support ticket if you need guidance!