PR Peter ReckersInnovatiepartner
← Alle projecten
42workspace · 4 juni 2026 · 6 min

Marvin: hoe mijn WhatsApp-lunchbot een persoonlijkheid kreeg

Het begon niet met techniek, maar met frustratie in de keuken. Iemand uit de 42 Workspace-gemeenschap kookte met liefde en overgave voor iedereen die aanschoof, met één simpele afspraak: meld je op tijd aan en neem boodschappen mee. Die afspraak brokkelde af. Aanmeldingen kwamen te laat of helemaal niet, boodschappen bleven uit, en het ding dat de community elke dag samenbracht dreigde te stoppen. Toen bood ik aan een bot te bouwen die voor structuur zou zorgen.

Het eindigde met een chronisch pessimistische robot die het weer checkt voordat hij een picknick durft voor te stellen. Dit is het verhaal van Marvin, een WhatsApp-bot voor de Lunch Club op 42 Workspace. Hij werkt, hij draait in Docker op een eigen server, en hij klaagt over alles. Precies zoals bedoeld.

Van "stuur een poll" naar een heel keukenregime

De eerste versie heette whatsapp-daily-poll en deed exact wat de naam zegt. Eén poll, elke ochtend: doe je mee met de lunch? Stem ja, kom op de gastenlijst. Klaar.

Maar een lunchclub is geen poll, het is een sociaal contract met boodschappen. Dus de logica groeide:

  • Ma–vr 09:00: lunchpoll open. Drie opties: voer me, ik eet zelf, of ik sla over.
  • Ma–vr 11:30: poll dicht, gastenlijst eruit, poll ingetrokken.
  • Ma 08:00: boodschappen-reminder, mét de namen van wie vorige week niets meebracht.
  • Vr 14:00: de wekelijkse afrekening: heb je boodschappen gedaan?
  • Zo 23:59: stil afsluiten, no-shows opslaan voor maandag.

De no-show-logica is het hart: wie ❌ stemt óf ✅ stemde voor de lunch maar de boodschappenpoll negeert, komt op de lijst. Behalve de kok: die voert iedereen, dus die hoeft geen boodschappen te doen. Eén vrije pas, hardcoded.

Het keukenkalender-experiment dat sneuvelde

De keuken gaat soms dicht. De kok is er niet, geen lunch. Mijn eerste idee: koppel het aan een agenda via CalDAV. Bot leest de kalender, ziet een afspraak, keuken dicht.

Mooi op papier. In de praktijk een bron van ellende. Agenda's syncen traag, een verwijderde afspraak bleef hangen, en ik zat constant te debuggen waarom de keuken "dicht" was terwijl er niets stond.

Dus weg ermee. Ik verving het hele kalenderverhaal door twee chatcommando's: iemand typt !closed en de keuken is dicht voor vandaag, !open draait het terug. Geen agenda, geen externe afhankelijkheid, geen sync-mysteries. Een datumstempel in een JSON-bestand dat om middernacht vanzelf verloopt. Soms is het simpele antwoord een chatregel, geen integratie.

Marvin betreedt het pand

De eerste versie draaide op mijn eigen WhatsApp-nummer. Slecht idee: berichten automatiseren op je eigen nummer is bij Facebook vragen om een ban. Dus kwam er een prepaid nummer. En een nieuw nummer heeft een identiteit nodig. 42 Workspace is gebaseerd op The Hitchhiker's Guide to the Galaxy, het boek waar een aantal droids in rondloopt. Eén daarvan is Marvin. De keuze was snel gemaakt.

Meteen heb ik ook alle saaie, functionele teksten omgeschreven in de stem van Marvin, de Paranoïde Androïde. Een brein zo groot als een planeet, en je laat 'm een lunchpoll draaien.

Het verschil zit 'm in de toon. De boodschappen-reminder is nu een roterende lijst, elke maandag een andere regel:

"Iedereen brengt deze week eten mee. Het is niet ingewikkeld, al heb ik jullie simpeler dingen ingewikkeld zien maken. Ik kijk toe, oordelend, zoals altijd."

En de keuken-dicht-melding:

"🚫 Keuken dicht. Sara is naar buiten — een plek waar naar verluidt 'weer' is. Je staat er alleen voor."

Niet alleen een grap. De bot voelt nu als een karakter in de groep in plaats van een script. Mensen reageren erop. Dat is het verschil tussen een tool en iets waar je een beetje van houdt.

Ook praktisch: ik splitste de groepen. De live-groep krijgt het volledige schema, een testgroep alleen de commando's. Zo kan ik draaien zonder dat de echte lunchclub mijn experimenten ziet.

De picknick die het weer leest

De leukste toevoeging: een optionele derde groep voor picknicks. De regel is streng. Marvin stuurt alleen een poll als het de moeite waard is.

  • Za 10:00: de bot haalt de zondagvoorspelling op via Open-Meteo (gratis, geen API-key). Droog én warm genoeg? Dan een poll, mét de voorspelling erin verwerkt. Anders: één regel "geen picknick", of helemaal stil bij slecht weer.
  • Zo 11:00: quorum-check. Genoeg ✅? Gastenlijst. Te weinig? Afgelast.

De afgelast-melding vat Marvin samen:

"Picknick afgelast. Ik zou zeggen dat ik teleurgesteld ben, maar ik verwachtte niets, en ik kreeg gelijk."

Daarbovenop twee commando's voor als je het weer wilt negeren: !picnic opent een poll voor vandaag (inclusief de actuele voorspelling, "weather be damned"), en !nopicnic trekt 'm weer in. Een aan- en uitknop.

Waar het echt vastliep

Bouwen op Baileys, een gereverse-engineerde WhatsApp Web-client, betekent dat je tegen de rauwe kanten aanloopt. De obstakels die me het meest kostten:

  • Geen push-notificaties. Groepsleden zagen de poll pas bij het openen van de app. Oplossing: markOnlineOnConnect: false. Zonder die vlag onderdrukt WhatsApp de meldingen voor bot-berichten.
  • Stemmen die zoekraken. WhatsApp synct poll-stemmen niet altijd naar een gekoppeld apparaat, en er is geen server-API om resultaten op te halen. Je mist soms een stem en kan niets reconciliëren. Daar leef je mee.
  • Namen in plaats van nummers. Niet elke stem draagt een naam mee. Ik bouwde een resolver die de cache naar schijf wegschrijft, zodat de gastenlijst over tijd namen toont in plaats van telefoonnummers.
  • Handmatige stem-decryptie. Baileys 6.7+ zette automatische poll-decryptie uit, dus dat doe ik nu zelf, LID-aware.
  • Nooit twee sessies tegelijk. Twee Baileys-sessies op hetzelfde WhatsApp-account corrumperen de Signal-sessiestaat. Dat heb ik één keer geleerd. Eén keer is genoeg.

In demo's van zo'n bot ziet alles er altijd soepel uit. In de praktijk werkte Marvin pas betrouwbaar nadat ik elk van deze vijf problemen had opgelost.

Waar het nu staat

Marvin draait als Docker-container op een eigen Hetzner-host, met alle staat in een gekoppelde data/-map zodat een herstart niets vergeet: de WhatsApp-sessie, de openstaande polls, de no-shows, de namencache. Deploy gaat via een Dokploy-compose-variant; het pakket heet inmiddels gewoon marvin.

Wat me eraan bevalt als bouwproject: het is klein genoeg om in één hoofd te passen, maar het raakt aan alles wat AI-werk in de echte wereld lastig maakt: onbetrouwbare protocollen, staat die je niet mag verliezen, en de afweging tussen een nette integratie en een chatregel die gewoon werkt. Soevereine tech op eigen server, geen cloud-lock-in, geen API-rekening.

En het belangrijkste resultaat staat niet in de code. Iedereen meldt zich tegenwoordig netjes aan, er zijn meer dan genoeg boodschappen, en de kok is weer tevreden. De lunchclub draait weer zoals hij bedoeld was. Alleen wordt iedereen nu elke maandag uitgekafferd door een depressieve robot. Dat is gewoon leuker dan een poll.