Creating your own shipping rate calculator for Shopify

There are a lot of shipping calculators on the Shopify App Store, but after trying a few out, I learned that none of them could do exactly what I wanted. So I decided to build my own.

NOTE: Unless you’re shipping USPS Media Mail, I would expect that you’d be able to find something in the Shopify App Store that would work for you. My very particular use case wasn’t covered by any existing apps, which is why I built my own.

I have a Rails app running on Heroku that handles a lot of small tasks for me. It connects several services I use, like Shopify, Drip, Zendesk, SyncSumo, and even a local 3PL company in Provo, UT that handles all my fulfillment. The app keeps expanding, and now also acts as a shipping calculator.

Overview of the CarrierService API

A custom shipping calculator relies on the CarrierService API, which, according to Shopify’s documentation, is only available to stores on the Advanced plan. This is a little misleading, because it’s actually availably to any store that has access to real-time carrier-calculated shipping rates, whether that store is on the Advanced plan or not. Last I checked, even if you’re on a month-to-month Basic plan, you can contact Shopify support and add real-time rates à la carte for a few extra bucks a month. Prepaying for a full year will get you the feature for free.

UPDATE: As of Feb 22, 2017, all stores in the US and Canada have access to calculated rates for USPS and Canada Post via Shopify Shipping. Click here to read more.

Once you have real-time rates enabled on your store, you can start using the CarrierService API. You register an endpoint (that belongs to your app) with Shopify that Shopify hit each time it needs some shipping rates (when someone is checking out). Shopify will send your app endpoint the cart contents, along with enough origin and destination information to get quotes from the carriers.

From there, it’s your job to then take that information, get the quotes from the carriers, and return it back to Shopify in a format it can read and display during checkout. Generally speaking, not too crazy.


Shopify uses ActiveShipping to power their own carrier-rate calculations in production, and they’ve chunked it out into a standalone gem you can use in your own projects.

The main purpose of this library is to talk to the different carrier services and return rates. This is obviously a nice thing to have because it means you don’t have to go out and write code to talk to each carrier individually. They’ve abstracted all that complexity into a nice library for you to use.

Once you set up the keys, tokens, and other authentication variables needed for each individual provider (a one-time thing), you just instantiate the class for the specific carrier service you want to talk to, and call `find_rates` while passing it the info Shopify sent you (origin, destination, and cart contents). The basic call looks like this:

response = usps.find_rates(@origin, @destination, @package)

Pretty nice, right? Then you just parse through the response to generate the object Shopify needs to be able to show the rates during checkout:

rates = response.rates.sort_by(&:price).collect {|rate| {
service_name: rate.service_name,
total_price: rate.price,
service_code: rate.service_code,
description: rate.description,
currency: rate.currency

Showing rates based on cart contents

So far, this setup would just return all USPS rates, even if they didn’t apply to your order. For example, it’ll return Library Mail and Media Mail if you’re shipping domestic. Library Mail is only for stuff sent between libraries, and media mail is only for things like books, CDs, DVDs, printed music, etc.

Lucky for me, I can use Media Mail for a majority of shipments because most of what I’m shipping are books. It’s a relatively cheap way to ship (usually around $2.67 to ship our heaviest book), and I offer it free to people in the US. It takes 2-8 business days, but some people are willing to wait if it means FREE. (I also offer the chance to upgrade to Priority for a flat fee–more on that later.)

But not all orders qualify for Media Mail. For example, if someone buys a book and a cross-stitch starter kit, the shipment is no longer eligible for Media Mail shipping.

A lot of the Shopify Apps allow you to customize rates shown based on cart contents. That’s not new. The reason I built my own shipping calculator is because none of those apps would also let me include Media Mail as a shipping option.

Filtering rates based on products

Once I return rates from the carriers, I send them through a few filters to make sure I’m only showing the rates I want to show. I have two main filters that the rates run through before being sent to Shopify:

Media Mail Filter

Like I said earlier, orders only quality for Media Mail shipping if they consist entirely of Media Mail eligible materials. I have an array of product IDs (stored as ENV variables) for each of the products that are Media Mail eligible. This filter runs through each product in the cart, and if any of them do not appear in the array of approved products, Media Mail is removed as an option.

Approved Shipping Rates Filter

I’m a huge believer in the Paradox of Choice, so I don’t show every rate that comes back. Just like I have an approved list of product IDs that are eligible for Media Mail, I have an approved list of shipping rates I want to provide, stored in an array. The list of rates returned by the carrier is run through this filter, which removes any rate that isn’t on my approved list (like Library Mail).

Promotions Filter

The last filter is one that applies any shipping promotions I’d like to offer. For example, if someone wants to upgrade from Media Mail shipping to Priority shipping, that’s an option. But I don’t charge them the full cost of Priority, since I’m already willing to eat the shipping cost of the Media Mail. This filter takes rates like the Priority rate and can either reduce it or make it a flat fee instead. If I wanted to, I could reduce the Priority rate by the exact amount that Media Mail would cost. For now, I just offer a flat fee.

Once the rates pass through these filters, the surviving rates are passed back to Shopify (they were formatted properly for Shopify when we parsed through the carrier responses earlier) and displayed during checkout.

Syncing Shopify customers to Facebook Custom Audiences

A little background on my current automation setup for my Stitch People Shopify store–I’ve written a custom Shopify app in Ruby on Rails to handle a lot of small tasks for me after someone makes a purchase. Unfortunately, this app is totally custom and wouldn’t work for anyone else, but eventually I’ll generalize it and throw it up on Github somewhere. The app currently helps connect Shopify to Drip, Facebook Custom Audiences, Zendesk, and more.

I’ve been waiting for a Shopify app to come along that will handle automatic syncing of customer data to Facebook Custom Audiences with advanced rules. There are a few out there that do this on a really basic level (like this one) but nothing yet that gives me the granular controls I’m looking for. So I built a service into my custom Shopify app that connects with SyncSumo.

SyncSumo is a service that lets you sync your custom audiences with outside data. They have a lot of different ways for accepting the data that needs to be synced to your audiences, and those integrations are focused mainly around email marketing and webinar software. I’m hoping they’ll eventually integrate with Shopify, but for now, they have a Webhook integration that lets you post some JSON data to an endpoint. This is what I’m using.

My custom Rails app receives webhook notifications from Shopify for new purchase events. When that happens, I just pull the email address out of the data Shopify sends me, repackage it into a JSON POST request, and then fire it off to the SyncSumo webhook endpoint. Within an hour, this email then gets synced to my Facebook Custom Audience.

I need more granular controls than other apps can give me because I’ve created custom audiences for each individual product purchased. Right now we have 4 pattern books, and I want to create audiences for each of them. There is definitely a lot of overlap in those audiences, but what I’m more interested in are the people who are in one audience but not in another. Those are perfect cross-sell potentials.

Our main product is our DIY book. It was the first book we released, and that’s the first book most people buy. We now have 3 other books that complement the DIY book in various ways with animals, military, and occupations and hobbies. When I have a custom audience for each of these products, I can create Facebook ads that target people who have purchased the DIY book, but NOT the Occupations and Hobbies book, or one of the other books.

Accessing transaction data in a Shopify email notification

Recently I needed to add a line to the Shopify Order Confirmation email that included the last 4 digits of the credit card used. This was more difficult than it needed to be, so I’m writing this post in hopes it’ll rise to the top in Google and help others trying to do the same thing.


You need to access the transaction object through a collection of transactions, like so:

{% for transaction in transactions %}
{{ transaction.payment_details.credit_card_number }}
{% endfor %}

This was stupidly difficult to track down. First, I went to the email variables article that’s referenced when you’re editing a notification. There, it states:

Notifications have access to every transaction property. Please see our Transaction API documentation here for a full list of the properties.

So I checked out that article, which didn’t help at all. Dead end.

After a few more searches, I finally found this thread in the forums that addressed my problem exactly. Reading through some of the comments, I thought I had hit another dead end, until I read the last comment in the thread, which pointed me to the thread that answered my question. Thanks to Mikkel Jakobsen for tracking down the answer!

Shipping a physical product

I never thought I’d be in the business of shipping a physical product. As a freelance developer, I always thought I would only ever offer services, or at most, a digital product like an e-book. So when my wife self-published the DIY Stitch People book, we had to figure out how to get those books from point A to point B.
When we first started selling the book, we didn’t have any sort of system set up. When we needed to ship a book, we’d go to a local place that handles everything from packaging to shipping. We’d buy a padded envelope, stick the book in, seal it, and then head to the front counter and read back the name and address of the customer while the person on the other side of the counter typed it in and generated a label. Then that person stuck the label to the envelope, threw it in a pile with other outgoing mail, and we paid for the envelope and postage.

This process sucked, especially when we had to ship out over 50 pre-orders at once. We were at the store for about 2 hours, and the receipt we got at the end was super long. Here’s a picture of the nice employee holding our receipt:

Stitch People Receipt

On that receipt were the tracking numbers for each package, too. So we went home and typed in each tracking number for each order and sent the customer their shipping confirmation email.

Lizzy and I quickly realized that this process wasn’t going to work. It was time-consuming, expensive, and error-prone. First, we found padded envelopes online at ULINE. Buying in bulk online was definitely cheaper than buying individual envelopes in the store. Problem solved (or so we thought–more on this later).

Next, we looked for a way to generate labels at home. That way, we wouldn’t have to spend so much time at the store reading off the names and addresses of each customer. We love podcasts, so we’d heard a lot about We signed up and started generating labels at home.

With our new envelopes and the ability to generate our own shipping labels, the next iteration of our shipping process was set. When an order came in, we’d throw a book into a ULINE envelope, go to, copy and paste all the customer information, generate the label, print it out, and tape it to the front of the envelope. Then we’d copy the tracking number and paste it into Shopify, which would send the customer their shipping confirmation email. And because we were using USPS (Media Mail, because we’re shipping books), we could leave the envelopes for our mailman to pick up, so we didn’t have to leave the house to ship. Not a bad improvement.

But this process got cumbersome after a while, too. The copying and pasting from Shopify to wasn’t very slick, and we were still making a few mistakes every now and then when generating labels. We figured that since the customer information was already in Shopify, there had to be a way to easily generate the label and update the order with tracking info without copying and pasting. So I went in search of another solution and landed on ShipStation.

I’ve written about ShipStation (and some of this other stuff) already in another post. It’s a really great solution and is what we currently use. When an order comes in, we go to ShipStation, where I’ve created presets for all the different options for shipping that we use and offer customers (international, media mail, priority, etc). It pulls the order data in from Shopify, we apply a preset, and then generate and print the label. As soon as the label is generated in ShipStation, it sends the tracking number back to Shopify, and the customer is sent their shipping confirmation email. Another big improvement.

When we first started using ShipStation, we were still printing labels on regular paper with our ink printer. It worked, but it was a hassle. The labels we generated were 4×6, not 8.5×11, so we were wasting a lot of paper. And taping labels to the front of the envelope sucked. Lizzy’s mom (who helps with fulfillment) suggested we look at a thermal label printer that worked with ShipStation. We found the Dymo LabelWriter 4XL on Amazon.

DYMO LabelWriter 4XL

With this thing and some cheap generic labels, things got really easy. Now we could process a batch of labels in ShipStation, the tracking numbers would get sent back to Shopify, and we could print on sticky labels without needing ink. The thermal-printed labels (the exact size of the generated label) just peel off and stick right to the package. This was a HUGE improvement for us.

The most recent thing we changed was the envelopes. We were getting complaints from customers that the packages were being mutilated in transit, arriving bent and creased. First we tried contacting USPS about it and learned that there’s nothing they can do. With Media Mail especially, our packages were being tossed around with much bigger and heavier packages. If we wanted to keep using USPS (they’re the cheapest option for us), we’d need to look at new packaging.

I checked out the ULINE catalog and found their Easy Folder Mailer boxes which work perfectly. It’s a tight fit because our book is spiral bound, but it protects the book so much better than the envelopes. It’s a bit more work to put the book into one of these things, but since we started using the boxes, we haven’t had a single complaint about the condition of the book when it arrives. For us, it’s definitely worth it.

And that’s where we are today. When an order comes in, we grab a box, fold it around the book, tape it shut, go to ShipStation, print out the label, slap it on the front, and leave it for the mailman. ShipStation talks to Shopify and sends over the tracking number, which is automatically sent to the customer. It’s not a perfect system, but we’ve found a nice rhythm to our shipping process. And I’m sure we’ll find other ways to improve as the need arises.

Do you ship a physical product? What would you suggest we do differently?

One Year Later

A year ago, I quit my job as a product manager at MoneyDesktop (now MX) to pursue a freelancing career, focusing on WordPress and Ruby development. Through a friend of a friend, I inherited a few spectacular clients that helped jumpstart my new business. Through word of mouth, the occasional Facebook post, and the professional network I had cultivated, I never wanted for new clients–in fact, I was lucky enough to start turning clients down when I realized I didn’t have the capacity to take them all on.
A year later, I’m still independent. About 25% of my time is spent working with some of those same clients. Most of them are at a point where they just need some minor tweaking rather than completely new functionality. I still have great relationships with all my current clients, and I even have a list of some other freelancers that I can pass potential clients on to. It’s perfect.

The other 75% of my time is spent working on Stitch People. If you’ve never heard me talk about Stitch People before, I wrote a post about it back in 2013. In the fall of last year, my wife self-published the first DIY Stitch People Book for people who want to learn to make these kinds of cross stitch portraits themselves. We realized that the business of actually making the custom portraits wasn’t nearly as scalable as producing a book on how to do it, so we focused our attention on the education aspect of Stitch People.

So far, we’ve sold over 1,000 physical copies of the book, and more than 500 digital copies. We’ve shipped books to over 20 countries around the world. Of the 103 reviews on the website, all of them are 5 stars. Needless to say, we’re happy with the success of this first book and are making plans for more books down the road.

My involvement in Stitch People is on the technical and marketing side of things. First and foremost, I make sure the website is working. We’re still with Shopify and probably will be for a while. Recently I looked at moving the store over to WordPress, but it just didn’t make sense. We like the Shopify experience, the mobile apps, and the peace of mind knowing that if our traffic spikes, we’re taken care of.

In addition to just keeping an eye on the site, I’m always running some sort of A/B test through Optimizely. Currently I’m running a test on whether or not calling out ‘Free Shipping in the US’ makes a difference on the book product page. So far, the results are pretty inconclusive, but it’s only been running for a day or so.

I also spend time every day looking at Google Analytics to see if there are areas or aspects of the site that need to be shored up. I look at bounce rates by platform and by referral source. If we’re seeing unusually high bounce rates from a specific source, I’ll dig into what might be causing it and see what I can do to fix it, like setting better expectations upfront through that source.

We’re running ads on several different networks. Facebook is our primary source of conversions, with Twitter and Pinterest contributing a handful every week as well. Twitter and Pinterest aren’t as cost effective as Facebook for us right now, so I’m experimenting with creative to see if there’s a better way to connect specifically to the Twitter and Pinterest crowds.

We’re also running some retargeting ads through AdRoll. Retargeting is a new area for me and I’ve been enjoying the learning process. Again, the biggest thing I’m focusing on here is experimenting with different creative to try to appeal to the retarget audience, both on Facebook and on the web at large. I think this is one area where we’ll eventually see some serious ROI once we get the formula right.

The last big thing we’re focusing on is the community around Stitch People. We have over 4,500 newsletter subscribers and great open and click rates for the industry we’re in. It’s an active and interested community, and we’re coming up with strategies for how to better communicate with them, allow them to communicate with each other, and really start a movement.

So between looking at and tweaking ad performance, coming up with new ad creative, checking analytics, doing research for some of our future plans, and trying to connect with our community, I manage to stay pretty busy. As for Lizzy, she’s the one fielding all the support emails, all the cross stitching, all the designing, and all the one-on-one connecting with community members.

It’s been a good year.