Engineering my Peloton delivery

TL;DR - I moved up my Peloton delivery date a month by polling their scheduling API for availability.

Last year I started doom-scrolling fitness strategies. I'm young enough to have stayed lean through sports, leisure, and a nice stroke of genetic luck. But my career in software is quickly replacing that with years of sedentary screen-staring and neck stiffening. Add a sweet tooth and an aversion to running... not a great long term health plan.

Enter Peloton. Past colleagues have tried to sell me on the ecosystem, but I've not been convinced it's worth the price of admission ($1,895 plus $39/mo at publish). Weighing that decision is beyond the scope of this article, but suffice to say, I went for it.

Quick note: I was able to get the friends/family discount on a refurbished Peloton Bike, which brought it down to $1,500. Not the selling point, but it helped.


If you've happened upon Peloton in the news this year, you'll know they've been inundated with orders since the World Lockdown™. Demand exceeds supply, and lead times go up. My Bike was set to arrive months after my order.

However... My order confirmation email had a link to Schedule or Manage an Existing Appointment:

From here, I could move my delivery to other available dates, all of which were later than my scheduled delivery. However, things can change - customers reschedule and cancel, suppliers restock.

I knew an earlier delivery might open up, but I didn't want to sit and watch their scheduling page until I found one.

So I automated it.

Grab Peloton's scheduling API call

On the Peloton schedule page, I glanced at the Network tab in Chrome's dev tools and found the GraphQL request that returns available delivery dates. (Replace YOUR_ORDER_ID_HERE with a valid Peloton order ID.)

curl --location --request POST 'https://graph.prod.k8s.onepeloton.com/graphql' \
\
--header 'authority: graph.prod.k8s.onepeloton.com' \
--header 'accept: */*' \
--header 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36' \
--header 'content-type: application/json' \
--header 'origin: https://www.onepeloton.com' \
--header 'sec-fetch-site: same-site' \
--header 'sec-fetch-mode: cors' \
--header 'sec-fetch-dest: empty' \
--header 'referer: https://www.onepeloton.com/' \
--header 'accept-language: en-US,en;q=0.9' \
\
--data-raw '{"operationName":"OrderDelivery","variables":{"isReschedule":true,"id":"YOUR_ORDER_ID_HERE"},"query":"query OrderDelivery($id: ID\u0021, $isReschedule: Boolean = false) {\n  order(id: $id) {\n    canSetDeliveryPreference\n    canReschedule\n    canSelectTimeSlot\n    deliveryPreference {\n      date\n      start\n      end\n      __typename\n    }\n    availableDeliveries(limit: 100, isReschedule: $isReschedule) {\n      id\n      date\n      start\n      end\n      __typename\n    }\n    __typename\n  }\n  postPurchaseFlow(id: $id) {\n    permission\n    __typename\n  }\n}\n"}'

Import the cURL into Postman

Postman offers free hourly API monitoring, so I created a Collection with this request in it, and setup an hourly Monitor on that collection. See the Postman Docs for more info.

With the hourly request running, I needed to know if an earlier delivery was available.

Add a Test to the Postman Request

Postman requests allow for custom tests written in JavaScript, and they run on every request execution. So I added a test to compare my current scheduled delivery with Peloton's latest available openings.

pm.test('newer date', () => {
    const json = pm.response.json();
    const current = new Date(json.data.order.deliveryPreference.date);
    const availString = json.data.order.availableDeliveries[0].date;
    const avail = new Date(availString);
    pm.expect(avail < current).to.eql(true);
});

This test will only pass if Peloton has an opening sooner than my scheduled delivery.

We could end here. Postman emails you the test results of your Monitors, so I would have been notified if the test succeeded. However, depending on the speed of their email service and my (lack of) desire to check personal emails, it could take awhile for me to see that notification. Really, I wanted a quick text message.

Setup Mailgun and Gmail forwarding directly to a cell phone

There are plenty of services that offer direct-to-SMS API calls, but they either cost money, or take awhile to configure. However, most phone carriers have an email service that allows customers to send/receive emails using SMS applications.

A few examples:

  • AT&T: phonenumber@txt.att.net
  • T-Mobile: phonenumber@tmomail.net
  • Sprint: phonenumber@messaging.sprintpcs.com
  • Verizon: phonenumber@vtext.com or phonenumber@vzwpix.com

The steps

  1. Tell the Postman test to hit the Mailgun API when the test passes.
  2. Have Mailgun send an email to my Gmail account.
  3. Setup Gmail to auto-forward Mailgun emails directly to my phone using Verizon's email service.

I setup a free Mailgun sandbox account. This provided me a URL and API key for sending emails using a RESTful network call.

Note: Mailgun's sandbox will only send emails to verified addresses. I had to add my Gmail address to the Mailgun app and verify it before I could send it a Mailgun email.

Then I added a Mailgun request to the Postman test. Here's the full test afterward:

// Replace MAILGUN_ID, EMAIL, and PELOTON_ORDER_ID with the correct values

pm.test('newer date', () => {
    const json = pm.response.json();
    const current = new Date(json.data.order.deliveryPreference.date);
    const availString = json.data.order.availableDeliveries[0].date;
    const avail = new Date(availString);
    pm.expect(avail < current).to.eql(true);

    pm.sendRequest({
        url: 'https://api.mailgun.net/v3/sandboxMAILGUN_ID.mailgun.org/messages',
        method: 'POST',
        header: 'Authorization: Basic MAILGUN_API_KEY',
        body: {
            mode: 'formdata',
            formdata: [
                { key: "from", value: "mailgun <mailgun@sandboxMAILGUN_ID.mailgun.org>" },
                { key: "to", value: "EMAIL@gmail.com" },
                { key: "subject", value: 'Peloton Available: '+availString },
                { key: "text", value: "https://www.onepeloton.com/delivery/PELOTON_ORDER_ID/reschedule" }
            ]
        }
    }, function (err, res) {
        console.log(res);
    });

});

And finally, I setup a Gmail forwarding rule to pass the notification to my phone:


The Results

I left the Postman Monitor running for a few days, but I never saw any texts come through. About a week in, though, Peloton must have had some big shipments or tons of cancellations because I started getting texts every couple hours.

My delivery date went from March 25 to March 23. A few hours later, March 17. The 16th. The 15th. The next day, another text - availability for February 24.

And so we got the bike a month earlier. Satisfying.

I've been riding it. The workout is good.