How to Detect and Escalate DHL Express Delivery Exceptions

Build a Spojit workflow that runs on a schedule, checks your in-transit DHL Express shipments, uses an Agent-mode node to decide which tracking events are real delivery exceptions, then opens a Front conversation and posts a Slack alert for every parcel that is genuinely stuck.

What This Integration Does

Parcels do not get stuck on a predictable timetable, and a raw DHL tracking feed is noisy: customs holds, weather delays, failed delivery attempts, and "address clarification required" events all sit next to routine "in transit" scans. Reading every event by hand does not scale, and waiting for the customer to complain means you find out too late. This workflow watches your active DHL Express shipments for you, lets an Agent-mode Connector node separate genuine exceptions from normal in-transit movement, and turns each real problem into a customer-service conversation in Front plus a heads-up in Slack so your team can act before the customer notices.

A Schedule trigger fires the workflow on a recurring cron (for example every two hours during business hours). On each run, the workflow pulls the list of shipments you are still tracking, calls DHL Express to track each one, asks an Agent-mode node to classify the latest events, and for any shipment flagged as an exception it creates a Front conversation and sends a Slack message. The workflow holds no long-term state of its own: it reads your tracking list fresh each run and acts on the current DHL status. Because nothing is permanently marked as "already alerted" inside the workflow, you control re-alerting by maintaining your own tracking list (the source you loop over), so closed or delivered parcels drop out and stop being checked.

Prerequisites

  • A DHL Express connection (API key) added under Connections, with access to the tracking API. See the article on adding a new connection.
  • A Front connection (API token) with permission to list inboxes and send messages, plus at least one channel you can send from.
  • A Slack connection authorized to post to the channel where your fulfillment team watches alerts. Note the channel ID.
  • A source of in-transit tracking numbers to check. This tutorial reads them from your Shopify store using list-orders, but any list of { trackingNumber, orderName, customerEmail } works.
  • A Spojit workspace where you can create workflows. If you are new, start with creating your first workflow.

Step 1: Start with a Schedule trigger

Add a Trigger node and set its type to Schedule. Enter a 5-field Unix cron expression and an IANA timezone so the run time is unambiguous. To check every two hours from 7am to 7pm on weekdays in Sydney, use:

0 7-19/2 * * 1-5
Australia/Sydney

A single Schedule trigger can hold more than one cron entry if you want different cadences on different days. The trigger output is just {{ scheduledAt }}; everything else this workflow needs is fetched live in the next steps. For more on cron fields, see setting up a schedule trigger.

Step 2: Pull the shipments you are still tracking

Add a Connector node in Direct mode, choose your Shopify connection, and select the list-orders tool. Filter to orders that have shipped but are not yet delivered, for example by setting fulfillment_status to shipped and a status of open. Map the result to a variable such as openShipments.

Each order carries its DHL waybill number on the fulfillment record. Add a Transform node to reshape the orders into a clean list your loop can read, keeping only what later steps need:

[
  {
    "trackingNumber": "{{ order.fulfillments[0].tracking_number }}",
    "orderName": "{{ order.name }}",
    "customerEmail": "{{ order.email }}"
  }
]

Save this as trackingList. If your tracking numbers live in another system, swap this connector for that one: the rest of the workflow only depends on the shape above.

Step 3: Loop over each shipment and track it with DHL Express

Add a Loop node in ForEach mode over {{ trackingList }}. Inside the loop body, add a Connector node in Direct mode using your DHL Express connection and the track-shipment tool. Map its fields from the current loop item:

  • trackingNumber: {{ item.trackingNumber }}
  • levelOfDetail: all so you receive the full event history, not just the latest scan

Save the tracking result to a variable such as tracking. This response contains the shipment status and the list of dated tracking events that the next step will judge. Keeping the DHL call inside the loop means one shipment looking up cleanly does not block the others. For looping details, see using loop nodes.

Step 4: Classify the events with an Agent-mode node

Still inside the loop, add a Connector node in Agent mode. Agent mode lets the agent read the tracking events and reason about whether they represent a genuine delivery exception, rather than you hard-coding a list of status strings that DHL can change. Turn on a Response Schema so the output is reliable JSON you can branch on:

{
  "type": "object",
  "properties": {
    "isException":   { "type": "boolean" },
    "severity":      { "type": "string", "enum": ["low", "medium", "high"] },
    "category":      { "type": "string" },
    "summary":       { "type": "string" },
    "lastEventDate": { "type": "string" }
  },
  "required": ["isException", "severity", "summary"]
}

Give it a precise prompt that names the data and the decision:

You are reviewing one DHL Express shipment for order {{ item.orderName }}.
Here are its tracking events: {{ tracking }}.
Decide if this shipment has a genuine delivery exception that needs a human
to act now (customs hold, address problem, failed or refused delivery,
damage, or no movement for more than 48 hours). Routine "in transit",
"arrived at facility", and "out for delivery" scans are NOT exceptions.
Return isException, a severity, a short category, and a one-line summary.

Save the output to verdict. To understand when to prefer this over a fixed tool call, read how to choose between Agent Mode and Direct Mode.

Step 5: Branch on the verdict with a Condition node

Add a Condition node that checks {{ verdict.isException }} is true. Wire the false branch straight to the end of the loop body so clean, in-transit parcels simply move on with no noise. Wire the true branch into the escalation steps below. This keeps escalation cost and alert volume tied strictly to real exceptions. See using condition nodes for operator details.

Step 6: Open a Front conversation for the stuck parcel

On the true branch, first add a Connector node in Direct mode on your Front connection using list-inboxes so you can confirm the channel ID you send from (do this once and hard-code the channel afterward if it never changes). Then add a second Front Direct mode node with the send-message tool to open the customer-facing conversation. Map:

  • channelId: the ID of the channel you send from
  • to: ["{{ item.customerEmail }}"]
  • subject: Delivery update for order {{ item.orderName }}
  • body: a short HTML message built from the verdict, for example <p>We noticed a delay with your shipment: {{ verdict.summary }}. Our team is on it.</p>

This creates a Front conversation your support team owns, so the customer thread and the internal follow-up live together. If you would rather only flag it internally, use add-tag on an existing conversation instead.

Step 7: Post a Slack alert to your fulfillment team

Still on the true branch, add a Connector node in Direct mode on your Slack connection with the send-message tool. Map:

  • channel: your fulfillment channel ID (for example C0123ABCD)
  • text: a one-line summary that carries the severity, order, and tracking number, for example:
:rotating_light: {{ verdict.severity }} DHL exception on {{ item.orderName }}
({{ item.trackingNumber }}): {{ verdict.summary }}

Because both the Front and Slack steps sit inside the loop, each stuck parcel produces exactly one conversation and one alert per run. When the loop finishes, the workflow ends; the next scheduled run starts the whole check again on a fresh tracking list.

Tips

  • Keep the Schedule cadence sane: tracking does not change minute to minute, so every one to three hours during business hours catches problems early without burning API calls or AI credits.
  • Pass the full event list to the Agent node (levelOfDetail set to all) so it can reason about "no movement for 48 hours," which a single latest-status field cannot reveal.
  • Use the severity field from the schema to route differently: send only high items to a louder Slack channel, and let low items just open a Front conversation quietly.
  • If you want a person to confirm before any customer message goes out, drop a Human approval node before the Front step on the true branch.

Common Pitfalls

  • Re-alerting: this workflow checks whatever is in trackingList every run, so a parcel stuck for days will alert on every run unless you narrow your source query (only open, undelivered orders) or move resolved parcels out of the list.
  • Missing waybills: orders fulfilled without a DHL tracking number will produce an empty trackingNumber. Add a Condition at the top of the loop to skip items where {{ item.trackingNumber }} is empty, so track-shipment is not called with a blank value.
  • Front channel mismatch: send-message needs a real channelId you are allowed to send from. If sends fail, run list-inboxes once and confirm the channel belongs to the right inbox.
  • Timezone surprises: a cron without a timezone runs against the workspace default. Always set the IANA timezone on the Schedule trigger so "business hours" means what you expect.

Testing

Before turning the schedule on, validate on a tiny scope. Temporarily replace the Shopify step with a Transform node that returns a one-item trackingList containing a real waybill you know is delayed (and one you know is fine). Use the Run button to execute once, then open the run in execution history and confirm that track-shipment returned events, that verdict.isException matches reality for each case, and that only the exception case reached the Front and Slack steps. When the verdict and routing look right, restore the Shopify step and enable the Schedule trigger. The article on understanding execution logs shows how to read each node's input and output.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.