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.

ActiveShipping

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:

usps = ActiveShipping::USPS.new(login: ENV['USPS_WEB_TOOLS_USERNAME'])
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.

Selling Digital Products on Shopify

I’m on my third digital delivery app for Stitch People, and I think I’ve finally found something that’ll work for me: SkyPilot. Here’s a review of what it does, why I like it, and why the other things I’ve tried have failed.

SkyPilot

Link: http://skypilotapp.com

This is my current app. I made the switch final today (May 15, 2016) and things are now running smoothly. Here’s what I love about it:

  • While the actual files are hosted and served through skypilotapp.com, this is all done behind the scenes and masked through your own domain. When people click on the link to download their file, the link is your domain. The SkyPilot app then intercepts that request and serves the necessary file.
  • The entire experience is on my domain. SkyPilot gives you templates to manage, so you have full control over how the experience looks for your customers. It integrates into your existing theme and layout, so there’s no break in the visual experience for your customers.
  • It integrates into the order status page. When someone completes their order and it contains digital products, they’ll see a button on the order status page that takes them to another page (on my domain) that lets the customer download the file. I still send a follow-up email (see the next point), but that’s now a backup. People can immediately download their files after their purchase without having to go back to their inbox and wait for a link.
  • I can manage the email notifications entirely. I’ve turned off email notifications through SkyPilot, and instead I send the download links through my email marketing service, Drip. When someone orders something through Shopify, Drip is notified and stores the customer ID. I then use that customer ID to generate their unique download link and send it to them. All emails go through my domain and have the highest deliverability rates possible. No more lost notification emails.

I’m hoping that I’ll stick with this one for a while. So far, it has everything I want, lets me see how many times someone has downloaded a file, logs the IP address of each download, and gives me the flexibility I want to craft the entire experience.

SendOwl

Link: http://www.sendowl.com

This was the second app I used, after Digital Downloads (below). It worked well for about 6 months, but eventually three things made me start looking for a third option.

  • The email notification sent to customers through SendOwl is sent from noreply@sendowl.com. They don’t have support for sending emails through your own domain, so even though I was able to set the ‘From’ name on the email, a lot of the download notification emails were going to customers’ spam folders. Not good.
  • Files were all hosted and downloaded directly on SendOwl.com. While they do offer some branding options, customers are ultimately leaving your site to go download their products elsewhere. This never really sat right with me.
  • Sometimes, customers would receive the email with the link, click on the link, and get a 404 error from SendOwl. Usually this resolved itself if the customer tried again in a few minutes, but this was completely unacceptable for me. A huge selling point to offering digital products it the immediate satisfaction a customer gets when they purchase and then download the file. A service that can’t reliably deliver a seamless experience isn’t worth my time.

Digital Downloads by Shopify

Link: https://apps.shopify.com/digital-downloads

This was the first app I used and it’s been probably 8 months since I’ve used it, but based on my experience, I have no desire to try it again. For me, the biggest issue was the inconsistencies with the download limit feature (which limits the number of times a customer can download a file). The default had it set to 3, and I kept getting emails from frustrated customers saying that the first time they clicked the link, they were told that they had exceeded the number of allowed downloads. I was going in at least once a day to reset someone’s download limit.

Another thing that drove me crazy: all downloads logged the IP address that download came from. Problem was, all downloads were being attributed to an IP address associated with Shopify. I emailed the Shopify team about this, and they confirmed that this was happening with every store that had the app installed. Several months after alerting them to the issue, it still hadn’t been fixed. Between this and the download limit weirdness, I decided that this app wasn’t worth the headache.

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.

TL;DR

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!

Why ConvertKit Wasn’t The Right Choice For Me

A few months ago, I wrote a post on switching from MailChimp to ConvertKit. At the time, I knew I needed to get off MailChimp, but didn’t know exactly what features I needed to be looking for. After using ConvertKit for five months, I’ve decided that I need to find something else. ConvertKit is great (and works really well for a lot of people like Pat Flynn), but I need a little more than what it has to offer. Namely:

  • Event tracking. It’s not enough to just tag a subscriber with some info (like ‘customer’ or ‘free-guide-download’). I want to know WHEN that tag was applied, when that subscriber converted, etc. Knowing when would help me craft better email sequences and create a more personalized experience for a subscriber.
  • A robust API. I’m starting to use a lot of different services to manage Stitch People, and am building custom solutions to integrate them. For example, we use Zendesk for our support emails. When someone writes in, we have an app that displays customer information about that person from our Shopify store. I wanted the same kind of functionality for people who purchased our products on Amazon, so I built my own app for that. The next app I build will be for pulling in data from our email marketing tool with all that event data I already mentioned. When did they subscribe? How many emails have they opened? How engaged are they in the brand? All this information will help me provide better customer service, but I can’t get that data without a good API. The ConvertKit API isn’t robust enough to handle what I need.
  • Subscriber meta fields. I need to be able to store more information about a subscriber than just name and email. I’d like to eventually build out my own abandoned email recovery tool and email sequence, and need to be able to save things like cart recovery URL for a subscriber.
  • Lead scoring. This was a feature I enjoyed in MailChimp, but didn’t think I needed. Turns out I really do need it. We have a Facebook group full of really great, engaged customers. MailChimp’s engagement scores helped me handpick that group, but now that I don’t have anything that shows me my most engaged subscribers, I don’t know who to invite into the group to keep things fresh.
  • Subscriber tracking. I’d love to close the loop on interactions subscribers have with my site. I’m segmenting people for Facebook Custom Audiences based on how they interact with my site, and I’d like to do that same kind of segmentation for my email list. For example, with the purchase of the DIY Stitch People book comes access to additional patterns available only online. I want to build a series of emails that can account for whether or not a subscriber has visited that URL yet. And what about customers who have already purchased one product, but are back on the website looking at other products? I’d like to proactively reach out to them based on what they were looking at.

Right now, it looks like Drip is going to be my best option. But before I commit to moving all my subscribers over, I’m going to do the free trial and see how things go. I’m also concerned that if I leave ConvertKit, all the links in emails I’ve previously sent won’t work anymore (like what happened when I left MailChimp), but that may be worth the benefits I’d be getting from Drip. More to come as I try out Drip and make a decision.