Skip to main content

Finding All Undelivered Messages

Overview

A common need with message campaigns involves pulling all of the undelivered messages for further analysis. This guide will show how you can use the SignalWire Python SDK and SignalWire PHP SDK in order to do exactly that.

Full code example: Pull Undelivered Messages
from datetime import datetime
from signalwire.rest import Client as signalwire_client
import pandas as pd

client = signalwire_client("ProjectID", "AuthToken",
signalwire_space_url='YOURSPACE.signalwire.com')

messages = client.messages.list(date_sent_after=datetime(2021, 10, 20),
date_sent_before=datetime(2021, 10, 30),
from_="+1xxxxxxxxxx",
)

d = []

for record in messages:
if record.status == "undelivered":
d.append((record.from_, record.to, record.date_sent, record.sid, record.error_code))


df = pd.DataFrame(d, columns=('From', 'To', 'Date', 'MessageSID', 'Error Code'))

print(df)

df.to_csv('UndeliveredMessages.csv', index=False, encoding='utf-8')

Python

What do we need to run this code?

For the following code to work, you will need to have pandas, DateTime, and the SignalWire Python SDK installed.

Read about the different ways to install pandas here.

Read about DateTime and how to install using pip here.

Read about the SignalWire Python SDK and how to install here.

Code Walkthrough

We will start by importing the necessary resources. In this example, that is DateTime, pandas, and the SignalWire Client. We also need to instantiate the SignalWire client using the project ID, auth token, and space URL.

from datetime import datetime
from signalwire.rest import Client as signalwire_client
import pandas as pd

client = signalwire_client("ProjectID", "AuthToken", signalwire_space_url = 'SpaceURL')

As this script involves pulling all messages and then filtering out the ones that are undelivered, it's very important to try and narrow down the results by using a date range, From numbers, or To numbers. DateSent is a DateTime object where the order for the arguments is Year, Month, Date, Hour, Minute, Seconds. You can leave hour, minute, and seconds at 0, unless you have a specific time of day you would like to filter by.

Octal Literals in Some Python Versions

For months Jan - September, A slight change was made because python version (3.9) does not support leading 0's in datetime anymore. You must use the 0o prefix for octal literals now. That is reflected below in the code. If your version doesn’t include that limitation, you can switch it back to 01 or whatever month you need.

The example below will show how to find all the messages within a specific date range that were sent from a specific From number.

messages = client.messages.list(date_sent_after=datetime(2021, 10, 20),
date_sent_before=datetime(2021, 10, 30)
from_="+1xxxxxxxxxx",
)

In the next section we will create an empty array d to keep track of our message data. We will loop through all the returned messages from the previous section and check for those where the status is undelivered. When a matches this condition, we will insert the data into the d array.

d = []

for record in messages:
if record.status == "undelivered":
d.append((record.from_, record.to, record.date_sent, record.sid, record.error_code))

You can expand this to include as many or as few parameters as you'd like. To see all of the parameters returned in the JSON response, you can view our API documentation here: List Messages API

Next, we will create a dataframe using pandas with column name headers. It's important to make sure that the order of the headers matches the order of the parameters you inserted into the array above. If you choose to add more or remove parameters, make sure to double-check that the order matches or your data will be mismatched in the CSV.

Lastly, we will print the dataframe for debugging purposes and export it to CSV. Using the parameter index=False turns off the indexing for each row.

df = pd.DataFrame(d, columns=('From', 'To', 'Date', 'MessageSID', 'Error Code'))

print(df)

df.to_csv('UndeliveredMessages.csv', index=False, encoding='utf-8')

PHP

What do we need to run this code?

For the following code to work, you will need to have the SignalWire PHP SDK installed. Read about the SignalWire PHP SDK and how to install here.

You will also need to make sure that the vendor/autoload.php path points to the correct location on your computer, as we can’t determine that for you. However, if you want to run this script exactly, install the SDK from within the folder that contains this PHP script.

Code Walkthrough

First, we need to import the necessary resources. In this example, that just means we need to point to the correct path of the vendor autoload file on your computer. We also need to instantiate the SignalWire client using the project ID, auth token, and space URL.

<?php

require './vendor/autoload.php';
use SignalWire\Rest\Client;
$client = new Client("ProjectID", "AuthToken", array("signalwireSpaceUrl" => "YOURSPACE.signalwire.com"));

Next, we need to choose what parameters we'd like to filter by. In this example, I have filtered by a date range and a from number to help narrow down the returned results. Depending on the volume of messaging that you send, you may have to restrict your date range down further so that you don't hit any memory allocation errors.

$messages = $client->messages->read([
"dateSentAfter" => "2021-04-01",
"dateSentBefore" => "2021-04-20",
'from' => '+1xxxxxxxxxx',
]);

In the next section, we need to create the file to insert the message data into.
Begin by writing headers and storing them in a variable called $fields. We also use implode to join the elements of the array with a string. We will then use fopen to create and open a file named TodaysDate_MessageReport. After that, we use fputcsv to insert our headers stored in $fields into our file.

$fields = array('Message SID', 'From', 'To', 'Date Sent', 'Status', 'Direction', 'Price');
echo '"'.implode('","', $fields).'"'."\n";

$fp = fopen(date("Y-m-d").'_messageReport.csv', 'w');

fputcsv($fp, $fields);

Next, we need to loop through our $messages array in order to gather the necessary message data. We will first check each message record to see what the message status is. If the status is undelivered, we will insert the message record into our CSV.

It is important to make sure the order of the headers is the same as the order of the values we will be gathering. As we loop through each message record, we will insert it into the CSV one by one and use implode to join the elements of the array with a string. The last step is to close the file!

foreach ($messages as $message) {
if ($message->status = "undelivered")
$row = array(
$message->sid,
$message->from,
$message->to,
$message->dateSent->format('Y-m-d H:i:s'),
$message->status,
$message->direction,
$message->price,
);

fputcsv($fp, $row);

echo '"'.implode('","', $row).'"'."\n";
}

fclose($fp);

Node.js

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 undeliveredMessages.js, for example, you then need to run:
node undeliveredMessages.js.

Code Walkthrough

Load the necessary libraries

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

Instantiate the SignalWire Rest Client

In order for us to connect to SignalWire later on in the code using the Rest Client we first need to make sure we update space_url, project_id, and access_token.

let space_url = "YOURSPACENAME.signalwire.com";
let project_id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX";
let access_token = "PTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

const client = RestClient(project_id, access_token, {
signalwireSpaceUrl: space_url,
});

Set the numbers to look for

In this step we need to create two arrays: one will contain all of the from numbers that we want to filter by and the other will be an empty array used for storing message records in greater detail.

Make sure to update the from_numbers array with your list of numbers, in E.164 format.

const from_numbers = ["+###########", "+###########", "+###########", "+###########"];

let data = [];

Loop through all of the numbers

For each of the numbers outlined in the previous step, we make use of the List Messages endpoint, through client.messages.list, and specify the from, dateSentAfter, and dateSentBefore parameters.

After we get the list of messages back, all messages where the status is undelivered are added to the data array.

Only once all of the numbers have been parsed do we get all of the data passed to the data array, and create the allData DataFrame using it. Using DataFrames allows us to easily print all of the message data, as well as export it to a CSV file.

Promise.all(
from_numbers.map((number) =>
client.messages
.list({
from: number,
dateSentAfter: new Date(Date.UTC(2022, 01, 01)),
dateSentBefore: new Date(Date.UTC(2022, 04, 25))
})
.then((messages) =>
messages.forEach((message) => {
if (message.status == "undelivered") {
data.push([
message.from,
message.to,
message.dateSent,
message.sid,
message.errorCode,
]);
}
})
)
)
).then(() => {
let allData = new dfd.DataFrame(data, {
columns: [
"From",
"To",
"Date",
"Message SID",
"Error Code",
],
config: {
tableDisplayConfig: {
columns: [
{ width: 1 },
{ width: 13 },
{ width: 13 },
{ width: 8 },
{ width: 36 },
{ width: 16 }
],
}
}
});

Lastly, we print the allData DataFrame to the terminal, and create the UndeliveredMessages.csv file using it.

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

Java

What do I need to run this code?

For the following code to work, you will need to have a Maven project, The Java Wrapper Library and a Signalwire account.

Read about how to get started with the Java Wrapper API here

Read on how to create a Maven project with Intellij here

How to get API Credentials

The API also requires that you authenticate yourself using your Project ID, API Token, and Space URL. If you do not know where to find these values, check out our guide here!

How to Run Snippet?

Assuming you've named your script MessageUndelivered.java you would need to do the following to run the javac MessageUndelivered.java then java MessageUndelivered.java.

Code Walkthrough

We also need to instantiate the SignalWire client using the project ID, auth token, and space URL

  public static List<String[]> dataLines = new ArrayList<>();

public static SignalWireClient client() {
return new SignalWireClient("YOUR-PROJECT-TOKEN",
"YOUR-API-TOKEN",
"YOUR-SPACE-NAME");
}

We need to get all the messages from the SignalWire space using the Java Wrapper library. added the below line of code.

List<Message> messages = client().message().getMessages().messages;

Loop through the messages and add an if statement to check if the element in the array has the status "undelivered"

 for (Message message : messages) {
if(message.status == "undelivered"){
dataLines.add(new String[]{message.from, message.to, message.date_created, message.to, message.sid});
}
}

Now we have the filtered result of the messages in our dataLines variable, we can then call the createPopulateCsv() method which is an helper method that generates and populate our csv.

   try {
createAndPopulateCSV();
} catch (IOException ioException) {
ioException.printStackTrace();
}

Full Java code:

import io.github.olajhidey.SignalWireClient;
import io.github.olajhidey.model.message.Message;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MessageUndelivered {

public static List<String[]> dataLines = new ArrayList<>();

public static SignalWireClient client() {
return new SignalWireClient("YOUR-PROJECT-TOKEN",
"YOUR-API-TOKEN",
"YOUR-SPACE-NAME");
}

public static void main(String[] args) {

List<Message> messages = client().message().getMessages().messages;


dataLines.add(new String[]{"From", "To", "Date", "Status", "MessageSID"});

for (Message message : messages) {
if(message.status == "undelivered"){
dataLines.add(new String[]{message.from, message.to, message.date_created, message.to, message.sid});
}
}

try {
createAndPopulateCSV();
} catch (IOException ioException) {
ioException.printStackTrace();
}

}

public static String convertToCsv(String[] data) {
return Stream.of(data)
.map(MessageUndelivered::escapeSpecialCharacters)
.collect(Collectors.joining(","));
}

public static void createAndPopulateCSV() throws IOException {

// Auto-generate the name of the file
String filename = "message_result" + System.currentTimeMillis();

// Get the absolute path to the folder you would like to create in the project directory
File file = new File(Paths.get("csv").toAbsolutePath().toUri());

// if file doesn't exist. create a new folder
if (!file.exists()) {
boolean isFolder = file.mkdir();
if (isFolder) {
System.out.println("New folder created!");
} else {
System.out.println("Couldn\'t create folder");
}
}

// create new csv file with the autogenerated name
File csvOutputFile = new File(Paths.get("csv").toAbsolutePath() + "\\" + filename);
try (PrintWriter printWriter = new PrintWriter(csvOutputFile)) {
dataLines.stream()
.map(MessageUndelivered::convertToCsv)
.forEach(printWriter::println); // Write item into the CSV file
System.out.println("Write operation done successfully!");
}

}

public static String escapeSpecialCharacters(String data) {
String escapeData = data.replace("\\R", " ");
if (data.contains(",") || data.contains("\"")) {
data = data.replace("\"", "\"\"");
escapeData = "\"" + data + "\"";
}
return escapeData;
}


}

Ruby

What do we need to run this code?

This snippet uses two gems SignalWire and Dotenv.

Additionally this snippet requires CSV which is part of the ruby standard gems

Code Walkthrough

Authentication

First we will load our required gems, and construct our SignalWire client using our credentials.

require 'dotenv/load'
require 'signalwire/sdk'
require 'csv'

username = ENV['projectID']
password = ENV['token']
host = ''
@client = Signalwire::REST::Client.new username, password, signalwire_space_url: host

requesting a list of messages

Next we can use our client to request a list of all messages in the project, as well as establish some helper variables

messages = @client.messages.list(    )

messageList = []

headers = ['sid','to','from','date']

# set status. Options: delivered, undelivered, sent, failed
status = 'undelivered'

filter messages and add them to our list

Then for each message SignalWire returns we can check the message.status, and add it to our messageList if it matches our desired status.

messages.each do|message|
if message.status == status
messageList.push([message.sid,message.to,message.from,message.date_sent,])
end
end

write our list to a csv

Finally, we can write each message from our filtered list to a CSV

CSV.open('undelivered-MSG.csv','w',headers: headers, write_headers:true) do |csv|
messageList.each do |message|
csv << message
end
end

Wrap up

Our List Messages API makes it easy to get lots of detail on your messages, and utilizing Python, PHP, or Node.js you can look for a particular status in particular, and get Messages SIDs for our Support team to investigate if needed!

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!