Tutorial: Printing with PrintNode

In this tutorial, we'll create a Sinatra application with the EasyPost Ruby gem that will list your Postage Labels, and give you the option to download the ZPL label files or print them using the PrintNode library.

Although we'll be using Ruby and Sinatra in this example application, this functionality could be integrated into any app written with Python, PHP, Java, and other languages with EasyPost's official client libraries and PrintNode's client libraries(opens in a new tab).

Before You Start

  1. Sign up for an EasyPost account or log in to an existing account.
  2. Read the Getting Started Guide.
  3. Walk through the PrintNode documentation and install the PrintNode client.
  4. Make sure you have Ruby and Bundler installed.

Step 1: Set Up Your Printer

The PrintNode documentation(opens in a new tab) has an in-depth guide, but given that we were using OSX, we found the below steps easier.

  • Download and install the PrintNode Desktop Client.
  • Open the Client and log in with your PrintNode account credentials.
  • Any installed printers should be visible in the printers tab.
  • Plug in your printer (in our case a Zebra ZP450)
  • Open a terminal and run lpinfo -v to find your connected printers, and look for a line that begins with direct usb://<printer connection>
  • Copy the usb://<printer connection> string and run lpadmin -p raw_label_printer -E -v usb://<printer connection>. Feel free to change the printer's name (in our example raw_label_printer) to another name if you prefer.
  • Provided things worked properly, you should now see a new printer named raw_label_printer available on the printers tab. Take note of the Id column, as we will be needing this in the next step.

Step 2: Get Your Keys

Our application will have a few details specific to our accounts which we will need to provide.

The first thing we need is an API key from EasyPost. This is an example application, so we're going to use the test API key, but be aware that non-test data will still be visible! You can find your test key on the API Keys page.

In order to print from our application, we'll use the PrintNode API. Go to printnode.com(opens in a new tab), sign up and download and install the PrintNode client(opens in a new tab) if you haven't yet. Once you're in, go to the API tab(opens in a new tab) and make a new API Key.

Next, make a new directory for you project, and open your favorite code editor and create a new file called .env in your new folder. Fill in the test EasyPost API key, PrintNode API key, and your printer's PrintNode Id, so it looks something like this:



Step 3: Creating the App

We'll start out by creating a Gemfile with the dependencies we will need. This will be a new file titled Gemfile in the project root directory. We can get away with a pretty minimal list, so let's add in the easypost and printnode gems to communicate with the respective APIs, the sinatra gem for our web application, and the dotenv gem to load our configuration values in. Your Gemfile should look like this:


source 'https://rubygems.org'
gem 'easypost'
gem 'printnode'
gem 'sinatra'
gem 'dotenv'

At this point, we can go ahead and install our dependencies by running bundle install, so let's do that.

Next, we can start writing our web application. Create a file called app.rb and save it in the project root directory. Here are the first few lines:


require 'sinatra/base'
require 'easypost'
require 'printnode'
require 'tilt/erb'
require 'dotenv'

class App < Sinatra::Base
  configure do
    EasyPost::Client.new(api_key: 'EASYPOST_API_KEY')
    set :printnode_client, PrintNode::Client.new(PrintNode::Auth.new('PRINTNODE_API_KEY'))
    set :printer_id, 'PRINTNODE_PRINTER_ID'

  get '/' do
    "Hello World! Our printer id is #{settings.printer_id}"

  # start the server if this file is executed directly
  run! if app_file == $PROGRAM_NAME

In these first couple of lines, we import the dependencies we installed, the app is defined (we're using Sinatra's modular style) and the config variables are loaded. We then instantiate the EasyPost and PrintNode clients with the keys from our .env

To see if it's all working, just type ruby app.rb from your project root, and you should see a few lines saying:

[2016-04-28 19:45:52] INFO  WEBrick 1.3.1
[2016-04-28 19:45:52] INFO  ruby 2.3.1 (2016-04-26) [x86_64-darwin15] == Sinatra (v1.4.7) has taken the stage on 4567 for development with backup from WEBrick
[2016-04-28 19:45:52] INFO  WEBrick::HTTPServer#start: pid=63201 port=4567

Go ahead and visit http://localhost:4567/ and you should see a message with the PrintNode printer id from our configuration.

When you've had your fill, stop the application with Ctl-C, and give yourself a pat on the back before moving on to adding a route for viewing our shipment labels, right below our root route in app.rb.


get '/shipments' do
  shipments = client.shipment.all
  erb :shipments, locals: { shipments: shipments }

These lines are getting a paged list of Shipments belonging to the current EasyPost API key, and then passing them to an erb view. We're doing the bare minimum here, so the default parameters of our client will be used. It should limit how many results we get and we won't bother paging any results, so don't worry if you don't see all your shipments. Be aware the default view will only show items for the past 30 days.

Let's create a simple view next. In your project folder, make a new directory called views and create a file named shipments.rb in it. In this file, we can create a simple html document that displays our items:


<!DOCTYPE html>
    <title>EasyPost PrintLabels</title>
          <th>Created at</th>
          <th>To Address</th>
        <% shipments.shipments.each do |shipment| %>
          <td><%= DateTime.parse(shipment.postage_label.created_at) %></td>
            <% addr = shipment.to_address %>
            <b><%= addr.name %></b><br />
            <%= addr.street1 %><br />
            <%= addr.street2 %><br />
            <%= addr.city %>, <%= addr.state %> <%= addr.zip %><br />
          <td><%= shipment.mode %></td>
            <% if shipment.postage_label.label_zpl_url %>
            <a href="/shipments/<%= shipment.id %>/zpl/print">print</a>
            <a href="<%= shipment.postage_label.label_zpl_url %>">download</a>
            <% else %>
            <a href="/shipments/<%= shipment.id %>/zpl/generate">generate</a>
            <% end %>
        <% end %>

You can feel free to make this look prettier, or lay it out as you like, but really we are just looping through our Shipments and printing out information. The important part here is the links we create for each Shipment. You'll notice that if a Postage Label has a label_zpl_url, we filled in links to print and download the file, and if it doesn't we provided a link to generate the ZPL. We will need to define one method to generate the ZPL, and one for the printing.

Let's quickly define a route to generate a ZPL formatted shipping label if one doesn't exist.


get '/shipments/:shipment_id/zpl/generate' do
  shipment = client.shipment.retrieve(params['shipment_id'])
  client.shipment.label(shipment.id, file_format: 'ZPL') unless shipment.postage_label.label_zpl_url
  redirect back

The code in this route finds our specific shipment, and makes a request to the EasyPost API to generate one unless a URL for a ZPL formatted label already exists on the Postage Label object. Once this request finishes, it redirects us back to the page we were on.

As a side note, if you were interested in generating a PDF format file instead, you could change the check to verify a label_pdf_url doesn't exist, and then change the file_format to 'PDF' in the EasyPost request.

Now that we can generate ZPL format labels for any Shipment that is missing one, lets move on to printing!


get '/shipments/:shipment_id/zpl/print' do
  shipment = client.shipment.retrieve(params['shipment_id'])
  printjob = PrintNode::PrintJob.new(
  settings.printnode_client.create_printjob(printjob, {})
  redirect back

Our final route that handles the printing looks fairly similar to our generate route, however instead of issuing a request to generate the ZPL file, it creates a printjob using PrintNode's API. We first look up our shipment using the id, then create a new PrintNode::PrintJob object, passing in our PrintNode designated printer_id that we configured earlier, our shipment's id as a job name, the 'raw_uri' printing format, our postage label ZPL url, and the app name 'PrintLabel' as our job source. We then take our PrintJob object, and pass it to the create_printjob method on our application's PrintNode client instance. When this is successfully sent, we redirect back to our listing page.

If you are interested in printing PDFs instead, note that similar to our generation, you can change the raw_uri to pdf_uri, and pass in the label_pdf_url instead.

Yep, that's it! If you have the PrintNode client running and configured, that's all the code you need to both generate ZPL format labels and get them printed!

Step 4: Starting the App

So now that it seems we have most of the functionality to demonstrate this, let's take our application for a spin!

As before run ruby app.rb from the project folder, and you should see your application start up.

[2016-04-28 19:45:52] INFO  WEBrick 1.3.1
[2016-04-28 19:45:52] INFO  ruby 2.3.1 (2016-04-26) [x86_64-darwin15] == Sinatra (v1.4.7) has taken the stage on 4567 for development with backup from WEBrick
[2016-04-28 19:45:52] INFO  WEBrick::HTTPServer#start: pid=63201 port=4567

Then just visit http://localhost:4567/shipments to see it in action!

We simplified things for the purpose of the tutorial, but you can view a more complete example in the repository we set up alongside this which is available on GitHub. The sample repo also demonstates paging through all your Shipments, and would serve as a great starting point to keep building off of!

Note that these printers can sometimes be a bit finicky, so if nothing is printing even though the PrintNode Desktop client is receiving the print requests (which is also viewable on the PrintNode dashboard(opens in a new tab)), we recommend power cycling your printer.