← Blog

Case study

WhatsApp-dispatch-agent: van 22 naar 4 minuten bij 3PL

Twaalf trucks. Drie dispatchers. Een WhatsApp-groep om 05:30 met tweehonderd berichten van chauffeurs die dezelfde vijf vragen stellen. Dit zetten we ervoor in de plaats.

Jacob Molkenboer· Oprichter · A Brand New Company· 3 jun 2026· 10 min
Koperen buispostkoker op groen leren onderlegger, gevouwen briefje met lint, rood wassegel, ivoorkleurig papier.

Het depot om 05:30

Twaalf line-haul trucks staan stationair te draaien. Drie dispatchers. Eén WhatsApp-groep met tweehonderd ongelezen berichten van de avond ervoor. De helft zijn chauffeurs die vragen waar ze als eerste moeten ophalen. Een kwart zijn chauffeurs die al onderweg zijn en de volgende drop-lijst nodig hebben. De rest zijn foto's van beschadigde kratten, kapotte pallets en stops met de verkeerde postcode.

Onze klant runt een regionaal logistiek bedrijf van 38 mensen in het zuiden van Nederland. Last-mile en B2B-palletwerk, zes magazijnen, zo'n 90 ritten per dag. Elke ochtend duurde de dispatch-overdracht (chauffeur verlaat het terrein met een bevestigd manifest en route) gemiddeld 22 minuten. Ze hadden het zelf twee weken lang opgemeten. Op maandagen lag het dichter bij 35.

Twaalf weken later is de gemiddelde overdracht 4 minuten, lossen chauffeurs 80% van hun vervolgvragen zelf op via een WhatsApp-agent, en is het dispatch-team om 05:30 van drie naar één persoon gegaan. Hier is precies wat we bouwden, en wat we onderweg fout deden.

De 22 minuten, geaudit

We weigerden te starten voordat we wisten waar de tijd daadwerkelijk heen ging. We zaten vier ochtenden in het dispatch-kantoor met een stopwatch, een notitieblok en een geprinte kopie van elk WhatsApp-gesprek van de week ervoor. Het resultaat was weinig flatteus.

  • 9 minuten per overdracht was een dispatcher die een vraag las, het TMS in een ander tabblad opende, een veld kopieerde en terugplakte in WhatsApp.
  • 5 minuten was de vraag van de chauffeur verduidelijken omdat het bericht een spraakmemo was, een half bijgesneden foto, of het verkeerde voertuig-ID.
  • 4 minuten waren statusvragen ("Ben je al onderweg?", "Heb je gelost in Tilburg?") waar het TMS het antwoord al op wist.
  • 3 minuten was het écht moeilijke deel: een uitzondering, een beschadigde drop, een klantgesprek.
  • 1 minuut was daadwerkelijk papierwerk: het manifest aftekenen.

Alleen die laatste 4 minuten hadden een mens nodig. De rest was de dispatcher die fungeerde als trage vertaler tussen een telefoontoetsenbord en een Postgres-database.

Waarom WhatsApp en geen chauffeursapp

De klant had twee keer een chauffeursapp geprobeerd. Beide keren stierf 'ie binnen zes maanden. Chauffeurs negeerden push-meldingen, hielden het icoontje op het vierde startscherm en gebruikten alsnog de WhatsApp-groep. De les zat al in de data: elke chauffeur, elke shift, zat binnen de eerste dertig seconden na het starten van zijn truck op WhatsApp.

We bouwden de agent bovenop de WhatsApp Business Cloud API. Chauffeurs sturen berichten naar hetzelfde nummer dat ze al drie jaar gebruikten. Het nummer is ongewijzigd. Er valt niets te installeren. Er is geen inlogscherm. Er is geen "update de app, alstublieft"-frictie.

Takeaway

De juiste interface is meestal degene waar je gebruikers al in zitten. De verkeerde interface is degene die je productteam graag bouwt.

De vorm van de agent

De agent doet vijf dingen. We lieten 'm in v1 niet meer doen, ook al hadden we dat gekund, want elke extra capability is een nieuwe failure mode op maandagochtend 05:30.

  1. Beantwoordt "waar moet ik als eerste heen?" met de eerste stop, het adres, de poortcode en de contactpersoon.
  2. Beantwoordt "wat is mijn volgende drop?" door de vorige stop af te ronden en de volgende terug te geven.
  3. Accepteert een foto van een beschadigd item, logt 'm tegen de huidige stop en seint de customer-success-inbox in.
  4. Accepteert "ik ben laat" of vergelijkbaar en herrekent de ETA voor de opvolgende stops.
  5. Escaleert alles wat 'ie niet begrijpt binnen 30 seconden naar een menselijke dispatcher, met de volledige berichtgeschiedenis erbij.

Al het andere (chauffeurswissel, voertuigwissel, geblokkeerde tankpassen, custom verzoeken van een klant) gaat naar een mens. De agent probeert nooit slim te zijn over het onhappy path.

De Postgres-view die het meeste werk deed

Het bestaande TMS was een PHP-applicatie uit 2014 op MySQL. Daar bleven we vanaf. We repliceerden de vier tabellen die we nodig hadden (routes, stops, drivers, vehicles) naar een Postgres-database via een Debezium change-data-capture-pipeline, en we bouwden één materialised view genaamd driver_now.

CREATE MATERIALIZED VIEW driver_now AS
SELECT
  d.id              AS driver_id,
  d.whatsapp_number AS phone,
  d.full_name       AS driver_name,
  r.id              AS route_id,
  r.shift_start_at  AS shift_start,
  v.plate           AS vehicle_plate,
  -- The next pending stop, or NULL if route is done
  (
    SELECT jsonb_build_object(
      'stop_id',   s.id,
      'sequence',  s.sequence_no,
      'customer',  s.customer_name,
      'address',   s.address_line,
      'postcode',  s.postcode,
      'city',      s.city,
      'gate_code', s.gate_code,
      'contact',   s.site_contact,
      'window',    tstzrange(s.window_start, s.window_end),
      'notes',     s.driver_notes
    )
    FROM stops s
    WHERE s.route_id = r.id
      AND s.status   = 'pending'
    ORDER BY s.sequence_no
    LIMIT 1
  ) AS next_stop,
  (SELECT COUNT(*) FROM stops s
     WHERE s.route_id = r.id AND s.status = 'done')    AS stops_done,
  (SELECT COUNT(*) FROM stops s
     WHERE s.route_id = r.id AND s.status = 'pending') AS stops_left
FROM drivers d
JOIN routes  r ON r.driver_id = d.id AND r.shift_date = CURRENT_DATE
JOIN vehicles v ON v.id = r.vehicle_id;

CREATE UNIQUE INDEX driver_now_phone_idx ON driver_now (phone);

Deze view wordt elke 30 seconden ververst met REFRESH MATERIALIZED VIEW CONCURRENTLY. De agent bevraagt het live TMS nooit. Hij zoekt de chauffeur op via diens WhatsApp-nummer, krijgt één rij terug, en die rij heeft alles wat nodig is om 80% van de inkomende vragen te beantwoorden. Read-only, geïndexeerd, voorspelbaar.

De truc is dat het zware werk (routes joinen met drivers met stops, sorteren op sequence, filteren op status, beslissen wat "nu" betekent) in de database leeft. De agent redeneert niet over planning. Hij leest een rij.

De agent zelf

De agent is een dunne worker die zich abonneert op de WhatsApp Cloud API-webhook, het inkomende bericht classificeert, één van vier tools aanroept en antwoordt. We gebruiken een klein, op Nederlands getuned model voor classificatie en een groter model alleen voor de schadefoto-flow, waar we vision nodig hebben en een zorgvuldiger antwoord. Hier is het volledige tool-oppervlak:

type Tool =
  | { name: "get_next_stop";    args: { phone: string } }
  | { name: "mark_stop_done";   args: { phone: string; stop_id: string } }
  | { name: "log_damage";       args: { phone: string; stop_id: string; photo_url: string; note?: string } }
  | { name: "delay_route";      args: { phone: string; minutes: number } }
  | { name: "handoff_to_human"; args: { phone: string; reason: string } };

Elk tool-resultaat is een klein JSON-object. De antwoordtekst wordt ingevuld door een deterministische formatter, niet door het model. Het model kiest de tool en de parameters. De woordkeuze van het antwoord is een Nederlandse template die het dispatch-team zelf heeft geschreven en goedgekeurd. Dit is de belangrijkste architectuurkeuze in het hele systeem.

Waarschuwing

Een LLM een antwoord laten formuleren met daarin een adres, een poortcode of een tijdvenster, is hoe je een "94% betrouwbare" agent live zet die op vrijdagmiddag een chauffeur de verkeerde postcode geeft. Gebruik deterministische templates voor elk veld dat een chauffeur kopieert of waar 'ie naar handelt.

De shadow-run van elf dagen

We zetten geen knop om. Elf werkdagen lang ontving de agent elk WhatsApp-bericht en produceerde 'ie een antwoord, maar dat antwoord ging naar een privékanaal dat de dispatchers in de gaten hielden. De dispatchers beantwoordden de chauffeur zelf. Na elke interactie tikte de dispatcher één van drie reacties op het concept van de agent: groen (zou ik hebben verstuurd), geel (dichtbij, had ik nog getweakt), rood (fout).

Op dag 4 zaten we op 71% groen. Op dag 9 op 92%. De gele berichten waren bijna allemaal dezelfde drie dingen: chauffeurs die vroegen naar een stop waarvan het tijdvenster diezelfde dag was gewijzigd, chauffeurs die een bijnaam voor een klant gebruikten (de agent zocht de officiële naam op), en chauffeurs die een spraakmemo in een regionaal dialect stuurden dat het speech-model niet vatte.

De eerste twee fixten we in de view en in de lookup-tabel. De dialectkwestie accepteerden we. Alles wat de agent niet met hoge zekerheid kan transcriberen, escaleert naar een mens. Dat is één extra menselijke reactie per dispatcher per dag. Daar zijn we prima mee.

Drie dingen die stuk gingen

Alle drie zijn ze de moeite van het vertellen waard.

Het 24-uurs sessievenster

Het WhatsApp Business Platform laat je alleen free-form berichten sturen binnen een 24-uurs venster na het laatste bericht van de gebruiker. Daarbuiten heb je een goedgekeurde template nodig. Onze eerste versie probeerde om 05:15 een "goedemorgen, dit is je eerste stop"-bericht te sturen naar chauffeurs die de vorige middag voor het laatst met het systeem hadden geappt. Ongeveer een derde van die chauffeurs viel buiten het venster. De berichten faalden stilletjes.

De fix was tweeledig. We registreerden een kleine set templates (route_start, stop_next, delay_ack) bij Meta. En we verwachten nu dat de chauffeur eerst zelf bericht stuurt ("start" of een duim-emoji) om de sessie te openen. Chauffeurs leerden dit in twee dagen.

De materialised view-race

Twee keer in week één markeerde een chauffeur een stop als afgerond, vroeg meteen om de volgende stop, en kreeg dezelfde stop terug. De view was nog niet ververst. De fix was om de view te omzeilen voor de keten mark_stop_doneget_next_stop: als we een stop afronden, leest de agent de volgende stop rechtstreeks uit de onderliggende tabellen, met een row-level lock, en geeft 'm in dezelfde response terug. De view blijft snel voor de 95%-case. De keten blijft correct voor de 5%.

De foto-upload-pipeline

WhatsApp media-URLs zijn gesigneerd en verlopen na 5 minuten. Onze eerste schade-log-worker bewaarde de WhatsApp-URL in Postgres en probeerde 'm een uur later op het customer-success-dashboard te renderen. Overal kapotte afbeeldingen. We halen de foto nu op in de webhook-handler, pushen 'm naar S3, en bewaren de S3-URL. Drie regels code, een halve dag verwarring bij customer-success voordat we het doorhadden.

De cijfers, twaalf weken later

  • Gemiddelde overdrachtstijd: 22 minuten naar 4 minuten.
  • Dispatch-bezetting om 05:30: 3 personen naar 1 persoon. De andere twee starten om 08:30 en handelen de rest van de dag uitzonderingen af.
  • WhatsApp-berichten die de agent zonder mens afhandelt: 81% van inkomend, gemeten over de laatste 30 dagen.
  • Eén gemiste-stop-incident, week 6, veroorzaakt door een poortcodewijziging buiten kantooruren die niet via CDC binnenkwam. Aan de bron opgelost.
  • Chauffeurs-feedback verzamelden ze hiervoor niet formeel. We deden in week 10 een korte enquête en de meest gehoorde opmerking was dat de WhatsApp-groep rustiger aanvoelde, omdat de agent in DM antwoordt en niet in de groep.

De koptekstbesparing is reëel maar saai: twee dispatcher-shifts per dag, vijf dagen per week, plus een betekenisvolle daling in customer-service-belasting omdat schaderapporten nu binnen 30 seconden binnenkomen met foto, tijdstempel en stop-ID. Het interessante zit in het tweede-orde-effect. Chauffeurs ronden hun rit gemiddeld 12 tot 18 minuten eerder af, omdat de frictie van "wat is de volgende?" vragen weg is. Dat is ruwweg één extra drop per truck per dag.

Het draagbare patroon

Jij runt waarschijnlijk geen 3PL. Het patroon is hoe dan ook draagbaar. Drie stukken.

  1. Identificeer het moment in jouw operatie dat eruitziet als 22 minuten WhatsApp-pingpong. Het is bijna altijd een statusvraag, een "wat is de volgende?"-vraag, of een foto-van-iets-kapots-vraag.
  2. Bouw één read-only view bovenop je bestaande database die die vraag in één rij beantwoordt. Migreer je TMS, ERP of CRM niet. Repliceer alleen de vier tabellen die je nodig hebt.
  3. Zet er een dunne agent voor op het kanaal waar je gebruikers al in zitten. Laat het model de tools kiezen. Laat je templates de woorden kiezen.

Toen we dit voor de logistieke klant bouwden, bleven we onderschatten hoeveel van de winst kwam uit minder doen in de agent, niet meer. Een klein oppervlak met een deterministisch antwoordsjabloon versloeg elk "laat het model dat maar regelen"-prototype dat we probeerden. Als je naar een vergelijkbare build kijkt: de architectuurkeuzes rondom ons werk aan AI-agents zijn dezelfde die deze case stuurden. De vijfminuten-audit waarmee elk van deze projecten begint, is dezelfde die we hier draaiden: bij het team zitten, stopwatch, vier ochtenden, geen slidedeck.

Het kleinste wat je vandaag kunt doen: open je operatie-WhatsApp-groep, scroll twee weken terug, en tel hoeveel berichten statusvragen zijn waar een database het antwoord al op weet. Als het meer dan de helft is, heb je het project.

Kern

Zet een dunne agent op het kanaal dat je team al gebruikt, richt 'm op één read-only database-view, en laat templates de woorden kiezen.

FAQ

Waarom WhatsApp en geen eigen chauffeursapp?

Chauffeurs zaten al elke shift in WhatsApp. Twee eerdere chauffeursapps waren gesneuveld door installatie-frictie en genegeerde push-meldingen. Gebruikers ontmoeten waar ze al zijn, won het van een nieuwe voordeur bouwen.

Moet je het bestaande TMS vervangen?

Nee. We lieten het MySQL-TMS ongemoeid en repliceerden vier tabellen naar Postgres via change-data-capture. De agent leest alleen uit de replica, dus er is geen risico voor het system of record.

Hoe lang duurde de build van begin tot eind?

Twaalf weken van kickoff tot volledige cutover. Vier weken voor audit, schema en de eerste agent. Drie weken shadow mode. Twee weken fixes. Drie weken geleidelijke uitrol over de zes magazijnen.

En chauffeurs die geen Nederlands spreken?

Het classificatiemodel verwerkt korte berichten in Nederlands, Engels en Pools. Alles wat ambigu is, escaleert naar een mens. Antwoord-templates zijn tweetalig waar het chauffeurs-record een niet-Nederlandse primaire taal markeert.

Is de WhatsApp Business Cloud API gratis?

De API zelf rekent geen kosten per bericht voor user-initiated sessieberichten binnen 24 uur. Template-initiated berichten buiten dat venster worden per gesprek geprijsd door Meta. De tarieven staan in de WhatsApp pricing docs.

case studyai agentschat agentsautomationworkflowoperations

Iets bouwen?

Start een project