How To Properly Process a Delivery Receipt

OpenMarket – April 1, 2021

by Parker DeWilde

I was shocked to find out that not all of our customers were properly handling delivery receipts, but when I took a peek at our publicly available documentation I realized that it was missing some key steps. This article will hopefully help patch any holes and allow customers to handle DRs in the correct manner.

Delivery receipts are the primary way to get feedback about what happened to a message you sent to a handset. Our V4 HTTP API allows you to specify a URL in which to receive a callback when your message gets delivered (or fails).

The first thing you need to do to receive a delivery receipt is to have a server with an endpoint at the url you specify to receive the DR callback able to receive the HTTP POST request with a JSON body.

For the purposes of this blog, I will create an example using python with Flask due to brevity it allows. Other platforms can be used for the same effect. Me and PEP8 don’t get along, so bear with my formatting.

from flask import Flask, request

app = Flask(__name__)

# we will accept POSTs on the root context
@app.route('/', methods=['POST'])
def receiveDr():
 # JSON body will be automatically deserialized into Python data structures
 drJson = request.json
 return “OK”

# For dev purposes, we will use the built-in Flask web server. Production use cases should use a proper server such as gunicorn + nginx proxy
app.run(host='0.0.0.0', port=8080)

Most customers can get to this point correctly using our publicly available documentation, but the next part is where they usually mess up. Once we have the DR JSON, we need to do something with it. Apparently customers have been doing silly things like updating databases to mark the message as received or failed, triggering an email in case of a number no longer being deliverable, or feeding some sort of data visualization.

These are all wrong. To find the correct way to handle a delivery receipt, you need to look at the name. It is a receipt. To handle it you need a thermal printer. Thanks to U.S.C. 4-01.2021 A.K.A. the KTRA (Keep The Receipts Act), you need to retain all delivery receipts in physical form for 7 years in case of an audit by the FMA (Federal Messaging Agency).

The first step of course is to acquire a receipt printer. I would recommend an Epson TM-T88V Model M244A. It is widely used, has USB connectivity, and can be found on Ebay for less than $100.

I have timed the time to print a single receipt at 1,040 ms, so to find out the number of printers you will need, divide TPS / 0.962. I would recommend looking at average TPS and adding a little extra, and then queue up receipts to be printed at peak times. For example, a customer sending 1000 messages a second would want about 1040 printers in parallel to satisfy their loads, with possibly an additional 100 to allow for redundancy as well as addition of receipt paper as they run out. Each receipt is 13.2 cm, and a roll of paper is 230 feet or 7010 cm, yielding about 531 receipts per roll. Given 1.040 second per receipt, you will need an employee to replace the paper every 9 minutes and 12 seconds. Assuming it takes 15 seconds to do so, you get a duty cycle of 97.35% or an effective duration of 1,068 ms. You will need another employee for every 36 printers you have. For 1000 tps, that means a staff of 30 paper-refillers.

Once you have a receipt printer, you will need to plug it into your computer. Linux by default will not give your user permissions to access the device. Doing so is not too complicated:

$ lsusb
...
Bus 001 Device 009: ID 04b8:0202 Seiko Epson Corp. Interface Card UB-U05 for Thermal Receipt Printers [M129C/TM-T70/TM-T88IV]
...
$

A lsusb command will show your printer. We are interested in the vendor id and the product id, which are shown separated by a :. In my case the vendor id is 04b8 and the product id is 0202.

Once we have those, we need to create a group to add our user to, as well as a udev rule to give that group access to the device. Note that you need to put in the product id and vendor id for your printer which you got using lsusb.

$ sudo groupadd usbusers
$ sudo addgroup <YOUR_USERNAME_HERE> usbusers
$ sudo sh -c  'echo "SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"04b8\", ATTRS{idProduct}==\"0202\", MODE=\"0664\", GROUP=\"usbusers\"" > /etc/udev/rules.d/99-escpos.rules'

You will need to reload udev rules for them to take effect. The easiest way is to reboot your computer.

That’s the hard part! Now for a little python. First install the python-escpos library. This is how you will issue commands to your thermal printer from python.

from escpos.printer import *
# Substitute your vendor id and product id here!
p = Usb(0x04b8, 0x0202) # connect to printer, will fail if permissions not right
p.set() # reset the text properties
p.text("hello world\n")
p.cut() # cut the receipt with the automated knife if your printer supports it

You should now have a receipt! It’s not quite a delivery receipt yet though! Almost there!

Here is the entire code:

from escpos.printer import * # use for connecting to thermal printer
from PIL import Image # use for opening and scaling images for logo
from flask import Flask, request # use for exposing API

# this scales any image to fit on your printer. 512 width works well for mine, you may need to choose a different value
def getImage(fileName):
 MAX_WIDTH = 512
 img = Image.open(fileName)
 wpercent = (MAX_WIDTH / float(img.size[0]))
 hsize = int((float(img.size[1]) * float(wpercent)))
 img = img.resize((MAX_WIDTH, hsize), Image.ANTIALIAS)
 return img

#printer setup
p = Usb(0x04b8, 0x0202)
p.set()
app = Flask(__name__)

@app.route('/', methods=['POST'])
def receiveDr():
 drJson = request.json # get json from post request
 # most fields are stored inside of a wrapper object, unwrap for convenience
 mtStatus = drJson['deliveryReceipt']['mtStatus']
 # print a logo at the top of the receipt
 # you will need the image in the same directory as the python script
 p.image(getImage('logo.png'))
 # tells printer to center align text we put in
 p.set(align='center')
 # tells the printer to print text
 p.text("\nDELIVERY RECEIPT\n\n")

 # helper function to handle formatting for us
 def printItem(first, second):
  p.set(align='left', bold=True)
  p.text(first)
  p.set(bold=False)
  p.text(str(second) + "\n")

 # print the parts of the DR, we will break into sections with a couple of dividers
 printItem("TICKET ID: ", mtStatus['ticketId'])
 printItem("DLVR DATE: ", mtStatus['deliveryDate'])
 printItem("DLR  CODE: ", mtStatus['code'])
 printItem("DLR  DESC: ", mtStatus['description'])
 printItem("NOTE    1: ", mtStatus['note1'])
 p.set(align='center')
 p.text("\n===DESTINATION===\n\n")
 printItem("DEST ADDR: ", mtStatus['destination']['address'])
 printItem("DST CNTRY: ", mtStatus['destination']['alpha2Code'])
 printItem("DST OP ID: ", mtStatus['destination']['mobileOperatorId'])
 p.set(align='center')
 p.text("\n===SOURCE===\n\n")
 printItem("SRC ADDR: ", mtStatus['source']['address'])
 printItem("SRC  TON: ", mtStatus['source']['ton'])

 # CODE128 barcodes need to be prefixed with ‘{B’, it seems to be a quirk of the library
 p.barcode('{BAPRILFOOLS', 'CODE128', function_type="B", pos='OFF')

 # cut the receipt
 p.cut()

 # return OK to OM -- 200 is implied unless we change the response code
 return "OK"

# development server for testing
if __name__ == "__main__":
 app.run(host='0.0.0.0', port=8080)

All that is left to do is to call OpenMarket’s SMS API! Note that I am calling with campaign ID rather than source address, as branded messaging customers would. This is due to how my test account is set up. Most customers would specify a source address when calling our API.

curl -L -X POST 'https://smsc.openmarket.com/sms/v4/mt' \
-H 'Authorization: Basic YXByaWw6Zm9vbHM6KQ==' \
-H 'Content-Type: application/json' \
--data-raw '{
    "mobileTerminate": {
        "options": {
            "campaignId": "MYTESTCAMPAIGN",
            "note1": "I'\''m making a note here: HUGE SUCCESS"
        },
        "destination": {
            "address": "13605556564"
        },
        "message": {
            "content": "Hello World!",
            "type": "text",
            "validityPeriod": 3599
        },
        "delivery": {
            "receiptRequested": "final",
            "url": "http://my-totally-real-url.tld:9088/"
        }
    }
}'

An example of it working. I am using postman to make my api request. My phone number was obfuscated, but it is a working example:

A printed OpenMarket receiptA printed OpenMarket receiptA printed OpenMarket receipt

Happy printing and April Fools!

Credit:

See all tech blog posts

Related Content