Can Admin API trigger Local Delivery “Mark as delivered” and customer email?

Hi,

I use Shopify Basic and Shopify Local Delivery on my store: https://blomster-levering.no

In Shopify Admin, local delivery orders have a native button called “Mark as delivered”. When I click this manually, Shopify marks the local delivery order as delivered and sends the customer delivery confirmation email.

I want to build a small QR-code driver app:

  • each printed order sheet has a unique QR code
  • the delivery driver scans the QR after physical delivery
  • the driver does not have Shopify Admin access
  • the QR opens a restricted one-order page outside Shopify
  • the only action available is “Mark as delivered” for that one order

My question:

Is there any public Shopify Admin API, GraphQL mutation, REST endpoint, or supported app method that can trigger the exact same result as the native Shopify Admin “Mark as delivered” button for Local Delivery orders, including sending the same customer delivery confirmation email?

I am not asking about tags, notes, metafields, carrier tracking events, fulfillmentEventCreate, or a custom email unless the native action is impossible.

If it is possible, I would need:

  1. Exact endpoint/mutation name
  2. Official Shopify.dev documentation link
  3. Required access scopes
  4. Required ID type
  5. Confirmation it works for Local Delivery orders
  6. Confirmation it sends the same customer delivery confirmation email

If it is not possible, a clear confirmation of that would also be very helpful.

Thank you.

Hi @Blomsten_Inger_Hoyvi! I took a look into this for you - There’s no dedicated public Admin API mutation that maps one to one to the native “Mark as delivered” button. What the button does under the hood is record a delivered shipment event on the order’s fulfillment, and that status change is what sends the customer the local delivery confirmation email.

You can reproduce the exact same result with fulfillmentEventCreate. Pass the fulfillment’s ID (gid://shopify/Fulfillment/…) and status: DELIVERED. For a local delivery order this fires the same “Delivered” notification the button sends, so there’s no notifyCustomer flag to set, it happens automatically on the delivered transition. If you want the full native sequence you can send OUT_FOR_DELIVERY first, then DELIVERED.

mutation {
  fulfillmentEventCreate(fulfillmentEvent: {
    fulfillmentId: "gid://shopify/Fulfillment/1234567890"
    status: DELIVERED
    happenedAt: "2026-06-08T13:05:00Z"
  }) {
    fulfillmentEvent { id status }
    userErrors { field message }
  }
}

The mutation needs the write_fulfillments scope, the order has to already be fulfilled (the fulfillment must exist before you can add a delivered event to it), and the customer needs an email on the order. For local delivery the confirmation email is sent automatically when the order is marked delivered, as described in fulfilling local delivery orders, so you don’t need to toggle anything in notification settings. Send a real-time happenedAt, since events backdated by several hours can be treated as stale and skipped for the email.

For your QR setup, a custom app with an Admin API access token and write_fulfillments is the way to go on Basic. Your driver page calls your own backend, which holds the token and calls the mutation, so the driver never needs admin access to the store.

Hope this helps!

Thank you very much, this was really helpful.

I had been trying to find a clear answer on whether this was possible, and your reply explained it in a way that makes sense. This gives us a good direction for how to build the delivery/QR setup without giving drivers access to Shopify Admin.

Really appreciate you taking the time to look into it and explain it properly!

Update SOLVED and thank you, this helped point me in the right direction.

I managed to build this into a working QR-based local delivery confirmation app for our Shopify store.

The final setup is:

  • A Shopify custom app with Admin API access

  • A small Node / Express backend

  • Hosted externally with HTTPS

  • A QR code printed automatically on our internal order / delivery sheet

  • Driver scans the QR with a normal phone camera

  • A simple mobile page opens

  • Driver taps “Mark as delivered”

  • The backend finds the Shopify order and handles the delivery status through the Admin API

One important thing I discovered during the build was that I could not rely on having a normal Fulfillment ID available at print time.

For our local delivery orders, the order could still be unfulfilled and not have a normal Fulfillment object yet, even though Shopify had a local delivery / fulfillment order connected to it. So the QR code could not simply contain a Fulfillment ID.

The solution I ended up using was:

  1. The QR code only identifies the order securely.

  2. The backend looks up the Shopify order when the QR is scanned.

  3. The backend checks whether a usable Fulfillment already exists.

  4. If a Fulfillment exists, it uses that Fulfillment ID.

  5. If no Fulfillment exists, it queries the order’s Fulfillment Orders.

  6. It finds the open local Fulfillment Order.

  7. It creates a Fulfillment from that Fulfillment Order.

  8. Then it marks that Fulfillment as delivered using fulfillmentEventCreate with status DELIVERED.

So the working flow became:

QR scan
→ secure token identifies the order
→ backend finds the Shopify order
→ backend finds an existing fulfillment or creates one from the fulfillment order
→ backend calls fulfillmentEventCreate with DELIVERED
→ driver sees a confirmation page

The app uses these scopes:

read_orders
read_fulfillments
write_fulfillments
read_merchant_managed_fulfillment_orders
write_merchant_managed_fulfillment_orders

The final delivery mutation is essentially:

mutation MarkDelivered($fulfillmentEvent: FulfillmentEventInput!) {
  fulfillmentEventCreate(fulfillmentEvent: $fulfillmentEvent) {
    fulfillmentEvent {
      id
      status
      happenedAt
    }
    userErrors {
      field
      message
    }
  }
}

With variables like:

{
  "fulfillmentEvent": {
    "fulfillmentId": "gid://shopify/Fulfillment/...",
    "status": "DELIVERED",
    "happenedAt": "2026-06-10T00:00:00.000Z"
  }
}

I also added a safe mode setting.

In safe mode, the QR code, driver page and button all work, but the app does not actually mark the Shopify order as delivered. That made it possible to test the full QR flow without changing a real order too early.

For the printed QR code, I added a backend image endpoint that returns a PNG QR code. That QR code points to a signed secure delivery URL. I first considered storing random tokens, but switched to signed tokens so printed QR codes still work after a restart or redeploy.

So the biggest practical lesson was:

You are right that fulfillmentEventCreate is the final mutation needed to mark the delivery as delivered, but for local delivery orders I also had to handle the case where no Fulfillment exists yet. In that case, the app first creates the Fulfillment from the open local Fulfillment Order, then creates the delivered event.

The app is now working end-to-end in safe mode, including QR generation, QR printing, scanning and the driver confirmation page. The live delivery test is next, where I will enable real delivery mode and test it on an actual delivery after the driver has delivered the order.

Thanks so much for the write up, really interesting! I’m glad I was able to point you in the right direction and I’m sure others will appreciate you sharing your findings - cheers!