UPS Proof of Delivery (POD)

Background

In our standard operation, every time we ship an order through UPS to outer islands or the mainland we would have a tracking number from UPS that we would put on the invoice. We keep proof of deliveries once we confirmed that the order was delivered to the customers. Prior to my arrival, we had a worker that would look up each of the tracking number manually and download the Proof of Delivery (POD) from UPS website and attach it to the invoice. This was a time consuming effort that would need the worker to look up each tracking number and confirm that it was delivered.


Solution

UPS

I had tried asking my UPS representative if they had a proprietary solution that automatically send us the Proof of Delivery once a package has been delivered. The representative had said that there were only reports that we could run from their Quantum View dashboard and that an email notification could be delivered from the UPS portal (These email deliveries do not have any POD attachment to them)

Proof of Delivery from UPS website

UPS POD Picture.png

An Email Notification from UPS

UPS Email Notification.png


UPS API and Simplemail

After hearing the representative answer, I dug around and found the UPS API documentation that said that we can pull POD information from their API catalog. Along with UPS Tracking API, I also used Simplemail to read all the delivered notification emails and get the tracking number via regular expression (something that I had learned from another project).

Logical Diagram

Automate UPS POD Diagram.png|550x550

Find Tracking Number Python Script

# Code Sample 
# Test.py 
from simplegmail import Gmail
from simplegmail.query import construct_query
import re, json, base64, requests
from weasyprint import HTML
from bs4 import BeautifulSoup

def stringToBase64(s):
    return base64.b64encode(s.encode('utf-8'))

def base64ToString(b):
    return base64.b64decode(b).decode('utf-8')

def get_tracking(trackingNum, access_token):

    url = "https://onlinetools.ups.com//api/track/v1/details/"+ str(trackingNum) +"?locale=en_US&returnSignature=false&returnPOD=true"

    payload = {}

    headers = {
      'transId': 'testing',
      'transactionSrc': 'testing',
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + access_token
    }

    response = requests.request("GET", url, headers=headers, data=payload)

    if response.status_code == 200: 
        data_response = response.json()
        pod = extract_pod_content(response.text)
        return pod
    else: 
        return "FAILED" 

'''
Extract the POD HTML in base64
Returns a decoded string of html formatted pod content 
'''
def extract_pod_content(json_string):
    data = json.loads(json_string)

    # Assuming there's only one shipment and one package for simplicity
    shipment = data['trackResponse']['shipment'][0]
    package = shipment['package'][0]
    pod_content_base64 = package['deliveryInformation']['pod']['content']

    # Decode the base64 content
    return base64ToString(pod_content_base64)

'''
Strip all html brackets and return plaintext message of content
'''
def strip_html_beautifulsoup(html_string):
    soup = BeautifulSoup(html_string, 'html.parser')
    return soup.get_text()

def contains_mcinfo_ups_email(text):
  """
  Checks if a string contains the word 'mcinfo@ups.com' (case-insensitive).

  Args:
      text: The string to check.

  Returns:
      True if the string contains 'mcinfo@ups.com', False otherwise.
  """
  
  return "mcinfo@ups.com".lower() in text.lower()

'''
STEP ONE: GET ACCESS TOKEN 
'''
# Prod site 
url = "https://onlinetools.ups.com/security/v1/oauth/token"

payload = {
  "grant_type": "client_credentials"
}

headers = {
  "Content-Type": "application/x-www-form-urlencoded",
}

response = requests.post(url, data=payload, headers=headers, auth=('[CLIENT ID]','[CLIENT SECRET]'))

data = response.json()

# Get access token to make API calls 
access_token = data['access_token']

'''
STEP TWO: Use Simplegmail to read through UPS emails and get tracking numbers and download and get pod and print to PDF 
'''

gmail = Gmail() # initial - open a browser window to ask you to log in and authenticate

# read unopen emails in the last two days
query_params = {
    "newer_than": (2, "day"),
    "unread": True,
    "category":"Primary",
}

# Message result string 
result_message = ""

messages = gmail.get_messages(query=construct_query(query_params))
for message in messages:
    if contains_mcinfo_ups_email(message.sender):
        plaintext = strip_html_beautifulsoup(message.html)
        # Regex that matches UPS Tracking Numbers 
        pattern = r"\b[A-Z0-9]{18}\b"
        trackings = re.findall(pattern, plaintext)
        for tracking in trackings:
            pod_result = get_tracking(tracking, access_token)
            if pod_result == "FAILED":
                result_message = result_message + "PARSE FAILED FOR TRACKING NUM " + str(tracking) + " \n"
                continue
            else:
	            # Found POD content and save it as PDF 
                HTML(string=pod_result).write_pdf(str(tracking)+'.pdf')
                result_message = result_message + "SUCCESSFULLY PARSED " + str(tracking) + "\n"
        message.mark_as_read()
    else:
        result_message = result_message + '** DELETED EMAIL FROM ' + str(message.sender) + '\n'
        message.trash()

params = {
  "to": "[YOUR RECIPIENT EMAIL]",
  "sender": "[YOUR SENDER EMAIL]",
  #"cc": ["[RECIPIENT EMAIL \#1]", "[RECIPIENT EMAIL \#2]"],
  "subject": "AUTOMATED - FOUND AND SAVED PODs",
  "msg_plain": "THIS IS AN AUTOMATED GENERATED EMAIL BY TUAN GIANG \n\n" + result_message,
}

message = gmail.send_message(**params) 

Move PDF Python Script

import os, shutil, re, json, base64, requests  
import datetime  
from simplegmail import Gmail  
from simplegmail.query import construct_query  
  
def strip_first_2_characters(string):  
return string[2:]  
  
def get_pdf_files():  
  """Gets all PDF files in a given directory.  
  
  Args:  
    directory: The directory to search.  
  
  Returns:  
    A list of PDF file paths.  
  """  
  
  pdf_files = []  
  for file in os.listdir('.'):  
    if file.endswith(".pdf"):  
      pdf_files.append(file)  
  return pdf_files  

gmail = Gmail() # will open a browser window to ask you to log in and authenticate     
root_directory = "/home/user/Storage/All Signed Invoices/"  
destination_directory = root_directory + "/UPS PODs"
  
result_message = "Successfully moved the following files to " + destination_directory + " : \n"  
  
pdf_files = get_pdf_files()  
  
for pdf_file in pdf_files:  
  result_message = result_message + pdf_file + ' \n'  
  shutil.move(pdf_file, destination_directory)  
  print("Successfully moved " + str(pdf_file))  
  
params = {
  "to": "[YOUR RECIPIENT EMAIL]",
  "sender": "[YOUR SENDER EMAIL]",
  #"cc": ["[RECIPIENT EMAIL \#1]", "[RECIPIENT EMAIL \#2]"],
  "subject": "AUTOMATED - Moving PODs PDF",  
  "msg_plain": "THIS IS AN AUTOMATED GENERATED EMAIL BY TUAN GIANG \n" + result_message,  

}  
  
message = gmail.send_message(**params)  # equivalent to send_message(to="[you@youremail.com](mailto:you@youremail.com)", sender=...)  

Crontab Code Sample

# Find Tracking Number from Email and Save it as PDF findtracking.py 
0 18 * * * cd /home/user/Project/Simplemail && /usr/bin/python3 /home/user/Project/Simplemail/findtracking.py >> /home/user/Project/Logs/UPS.log 2>&1 

# Move PDF files to desired path 
30 18 * * * cd /home/user/Project/Simplemail && /usr/bin/python3 /home/user/Project/Simplemail/movepdf.py >> /home/user/Project/Logs/UPS.log 2>&1 


Conclusion

In summary, each time a package is sent through UPS, we utilize the UPS API and Simplemail to automatically download the Proof of Delivery from UPS' system. This significantly reduce the time for the employee whose task is to check for completed delivery and download the content which can take hours on end. The automation system above requires no human interaction and it fulfill all project requirement and specification defined from the human worker.

POD from the UPS WebsitePOD generated from UPS Tracking API