Engineering · 21 september 2026

E-mailwebhooks ontwerpen voor at-least-once-bezorging

Lees hoe je veerkrachtige webhookconsumers voor e-mailevents bouwt met retries, idempotentiesleutels en verificatie van handtekeningen, zodat je nooit een bezorgevent mist.

De uitdaging van betrouwbare eventbezorging

Voor at-least-once-bezorging van e-mailwebhooks heb je een systeem nodig waarin de verzender mislukte requests opnieuw probeert met exponential backoff en de ontvanger voor idempotentie zorgt. Omdat netwerken onbetrouwbaar zijn en servers crashen, kun je er niet van uitgaan dat één HTTP 200 OK garandeert dat het event is verwerkt. Betrouwbaarheid ontstaat door een persistente retryqueue aan de kant van de verzender te combineren met een deduplicatielaag aan de kant van de ontvanger.

Wanneer je een e-mail-API zoals SendHQ integreert, moet je applicatie weten wanneer een e-mail is afgeleverd, gebounced of als spam gemarkeerd. Deze events zijn asynchroon. Ligt je webhook-endpoint tijdens een verkeerspiek vijf minuten plat, dan kun je duizenden cruciale bezorgsignalen kwijtraken. Dat veroorzaakt een gat in je analytics en voorkomt dat je systeem op bounces reageert (wat essentieel is voor het behoud van je afzenderreputatie).

De anatomie van een betrouwbare webhook

Een robuuste webhookarchitectuur rust op drie pijlers: verificatie van handtekeningen, idempotente verwerking en een retrystrategie.

1. Verificatie van handtekeningen

Vertrouw een POST-request naar je webhook-endpoint nooit alleen op basis van het IP-adres of de aanwezigheid van een API-sleutel in de body. Aanvallers kunnen die vervalsen. Gebruik in plaats daarvan een HMAC-handtekening (Hash-based Message Authentication Code).

De verzender ondertekent de payload met een gedeeld geheim en zet de handtekening in een header (bijv. X-SendHQ-Signature). De ontvanger berekent de hash opnieuw met hetzelfde geheim en vergelijkt die met de header.

const crypto = require('crypto'); function verifySignature(payload, signature, secret) { const expectedSignature = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); // Use timingSafeEqual to prevent timing attacks return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature)); }

2. Idempotentie en deduplicatie

At-least-once-bezorging betekent dat de verzender het event blijft versturen tot hij een succesrespons ontvangt. Verwerkt je server het event maar crasht hij voordat de 200 OK wordt teruggestuurd, dan verstuurt de verzender het event opnieuw. Zonder idempotentie tel je één bezorging mogelijk als twee bezorgingen in je database.

Elk event moet een unieke event_id hebben. Gebruik een patroon met een idempotentiesleutel om verwerkte events bij te houden.

De workflow:

  1. Ontvang de webhookpayload.
  2. Controleer of de event_id al in je tabel processed_events staat.
  3. Staat die er al, retourneer dan direct 200 OK en negeer de body.
  4. Staat die er niet, verwerk dan het event en sla de event_id op in één transactie.

3. De retrystrategie

Voor de verzender is een retrybeleid verplicht. Een standaardpatroon is exponential backoff met jitter. Bijvoorbeeld: opnieuw proberen na 1 minuut, 5 minuten, 30 minuten, 2 uur en 12 uur.

Retourneert de ontvanger een 4xx-fout (behalve 429), dan wijst dat meestal op een clientfout (zoals een ongeldige handtekening) en heeft opnieuw proberen geen zin. Een 5xx-fout of een timeout wijst op een tijdelijke storing waarbij retries essentieel zijn.

Concreet voorbeeld van een payload

Dit is een typische payload van een bezorgevent die je van SendHQ kunt ontvangen:

{ "event_id": "evt_12345abcde", "event_type": "delivered", "timestamp": "2026-09-15T10:00:00Z", "message_id": "msg_98765xyz", "recipient": "user@example.com", "metadata": { "order_id": "ord_5544" } }

Fouten en randgevallen afhandelen

Het probleem van de "trage consumer"

Voert je webhookhandler zware databaseschrijfacties uit of roept hij synchroon andere externe API's aan, dan krijgt je endpoint een timeout. Dat activeert de retrylogica van de verzender en leidt tot een "retrystorm" die je server kan laten crashen.

De oplossing: ontkoppel acceptatie van verwerking.

  1. Ontvang de webhook.
  2. Verifieer de handtekening.
  3. Zet de ruwe payload in een message queue (zoals RabbitMQ, SQS of Redis).
  4. Retourneer direct 200 OK.
  5. Een apart workerproces leest de queue uit en werkt je database bij.

Het probleem van agentgereedheid

Wanneer AI-agents door webhooks worden aangestuurd, neemt het risico op oneindige lussen toe. Ontvangt een agent een "delivered"-event en reageert hij door nog een e-mail te versturen, die vervolgens weer een "delivered"-event oplevert, dan heb je een lus.

Behandel het versturen van e-mail als een extern neveneffect. Agents mogen nooit automatisch e-mails versturen op basis van een webhook zonder goedkeuring door een mens (human-in-the-loop) of een strikte controle via een state machine die bevestigt dat de actie nodig is.

Het ecosysteem vergeleken

Bij het kiezen van een provider hangt betrouwbaarheid vaak samen met hoe die provider deze events afhandelt en wat hij rekent voor het mailvolume dat deze events oplevert.

Voor transactionele e-mail met een hoog volume is het kostenverschil groot. Volgens de prijzen van Amazon SES kost verzenden a la carte 0.10 USD per 1.000 e-mails. De prijzen van Postmark beginnen daarentegen bij 15 USD per maand voor 10.000 e-mails, met overschrijdingen tussen 1.20 en 1.80 USD per 1.000. Voor een volume van 50.000 e-mails kost SES a la carte ongeveer 5 USD, terwijl de niveaus van Postmark ongeveer 66 USD zouden kosten.

Andere opties zijn Resend, met een gratis niveau van 3.000 e-mails per maand (met een maximum van 100 per dag) en een Pro-abonnement van 20 USD per maand voor 50.000 e-mails. Mailgun begint bij 15 USD per maand voor 10.000 e-mails. SendGrid heeft het gratis niveau omgezet in een proefperiode van 60 dagen, met Essentials vanaf 19.95 USD per maand.

Ongeacht de provider bepaalt de betrouwbaarheid waarmee jij deze events verwerkt de integriteit van je data.

Implementatiechecklist voor engineers

  • Handtekeningverificatie: Wordt de payload geverifieerd met een gedeeld geheim en een constante-tijd-vergelijkingsfunctie?
  • Asynchrone verwerking: Geeft het endpoint 200 OK terug voordat zware bedrijfslogica wordt uitgevoerd?
  • Idempotentie: Is er een unieke beperking op event_id om dubbele verwerking te voorkomen?
  • Timeoutbeheer: Is de timeout lager ingesteld dan de timeout van de provider om overlappende retries te voorkomen?
  • Monitoring: Heb je meldingen voor een piek in 5xx-responses op je webhookendpoint?
  • DNS-status: Zijn je ontvangende servers correct geconfigureerd? Gebruik tools zoals de SendHQ Email DNS Checker om te zorgen dat je infrastructuur bereikbaar en correct geconfigureerd is.
  • Authenticatiestandaarden: Heb je DKIM, SPF en DMARC geïmplementeerd om te zorgen dat je uitgaande e-mail wordt geaccepteerd, zodat je minder "bounce"-webhooks hoeft af te handelen?

De afwegingen op een rij

Aanpak | Voordelen | Nadelen

Synchrone verwerking | Eenvoudig te implementeren, directe consistentie | Hoog risico op timeouts, gevoelig voor retrystorms

Verwerking via een queue | Zeer schaalbaar, bestand tegen pieken | Complexere infrastructuur, eventual consistency

Eenvoudige logging | Weinig overhead | Gemiste events niet te herstellen zonder handmatige logs

Idempotentietabel | Gegarandeerde data-integriteit | Extra databaseschrijfactie per event

Tot slot

Betrouwbaarheid bij e-mailwebhooks draait niet om het voorkomen van fouten, maar om ontwerpen met fouten in gedachten. Door ervan uit te gaan dat het netwerk faalt en dat events meer dan eens worden bezorgd, bouw je een systeem dat echt veerkrachtig is. Of je nu SPF-records beheert voor een klein project of een enorm transactioneel systeem opschaalt: verificatie van handtekeningen en idempotentie blijven de gouden standaard.

Bouw je e-mailinfrastructuur met SendHQ.