Blue Reacher
Shopify45 / 50

Shopify

Abandoned checkout recovery, order updates, and post-purchase follow-up over iMessage, wired through Shopify webhooks.

What this covers

Three flows, in the order they are worth building: abandoned checkout recovery, order and delivery updates, and post-purchase follow-up. All three run off Shopify webhooks into a small handler that calls Blue Reacher.

Shopify's SMS marketing consent checkbox at checkout is the consent record for marketing messages. Read customer.sms_marketing_consent.state on every payload and send marketing only when it is subscribed. Transactional messages about an order the person placed sit under prior express consent, which is a different and lower bar, but keep the two flows separate in your code so a marketing message never rides on a transactional consent record. The distinction is in the compliance guide.

Abandoned checkout recovery

Subscribe to the checkouts/update webhook in Shopify (Settings, Notifications, Webhooks) pointed at your handler.

export default async function handler(req, res) {
  res.status(200).end();
  const checkout = req.body;

  if (checkout.completed_at) return;
  if (checkout.customer?.sms_marketing_consent?.state !== "subscribed") return;
  if (!checkout.customer?.phone) return;

  // wait out the window before sending; enqueue rather than sleep
  await queue.schedule("checkout-recovery", { checkoutId: checkout.id }, { delayMinutes: 60 });
}

Then, at send time, re-fetch the checkout and confirm it is still incomplete before messaging. Nothing damages a brand faster than a recovery message to someone who completed the order forty minutes ago.

await fetch("https://api.bluereacher.com/v1/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BLUEREACHER_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    to: checkout.customer.phone,
    message: `Hi ${checkout.customer.first_name}, it's Sam from Northside. Your cart's still saved if you want it. Anything I can answer?`,
    metadata: { shopify_customer_id: String(checkout.customer.id), flow: "abandoned-checkout" }
  })
});

Timing that works: one message at 60 minutes, one at 24 hours if there is no reply, then stop. A third message on an abandoned cart converts near zero and generates opt-outs.

Do not put the checkout link in the first message. Ask the question, and send the link when they reply, per links and previews.

Order and delivery updates

Subscribe to orders/paid and fulfillments/create. These are transactional, they are welcome, and they are the cheapest way to establish a thread that later marketing messages can live in.

// fulfillments/create
message: `Hi ${firstName}, your order just shipped. Tracking: ${trackingNumber}. Reply here if anything looks off.`

An established thread also improves link preview rendering and gets your saved-contact rate up, both covered in sender identity.

Post-purchase follow-up

Subscribe to orders/fulfilled, wait for the delivery window plus a few days, then ask one question. Not a review request as the first message: ask whether the product worked, and request the review from people who say yes. Same consent rule applies, since this is marketing.

Identity mapping

Upsert the customer into Blue Reacher contacts once with custom_fields: { shopify_customer_id }, and keep your own phone-to-customer map. Inbound events identify the contact by phone number, so your handler resolves the Shopify customer and can pull order history before a human answers.

Opt-outs back into Shopify

Subscribe to contact.opted_out on your Blue Reacher webhook and write it back to Shopify by setting the customer's SMS marketing consent to unsubscribed through the Admin API. A person who opts out of your texts should not keep receiving them from a different tool on the same stack. Sends are blocked platform-side regardless, per opt-out handling.

Checklist

  • Consent state checked on every marketing send
  • Marketing and transactional flows separated in code
  • Checkout re-verified as incomplete at send time
  • No link in the first message
  • shopify_customer_id stamped in contact custom_fields and send metadata across all flows
  • Opt-outs written back to Shopify

On this page